Skip to content

改写滚动动画写法

尝试用 vueuse 改造滚动动画,力求代码编写优雅简洁。

loading

改造思路

用数据驱动变化。用响应式数据去驱动样式的动态添加。

  1. 注意到 vueuse 的 useElementBounding 可以实现响应式地动态计算 dom 元素的值,在鼠标滚动时,就能够动态使用 Element.getBoundingClientRect() 函数动态地计算元素的 top 值。改造将围绕着该响应式值来完成。

  2. 先用 useTemplateRef 获取到全部的 dom 元素。先获取 dom 元素,再初始化全部的响应式数据。

  3. 遍历一次元素,唯一执行一次 useElementBounding ,生成出响应式变量。

  4. 最后到 dom 元素内,根据计算结果,动态添加 class。

改造优点

  1. 减少了手动监听滚动事件,和手动维护样式动态添加的心智负担。
  2. 思维模式仅考虑单纯的数据变化,而不是监听滚动事件。

源码

详情
vue
<script lang="ts" setup>
import { ref, computed, watch, useTemplateRef } from "vue";
import { useElementBounding, reactify } from "@vueuse/core";

/** 计算触发点的位置,通常在窗口底部的四分之一位置 */
const triggerBottom = (window.innerHeight / 5) * 4;

/**
 * 全部的滚动项dom元素
 * @description
 * 这里的内容全都是数组,数组中的每一项都是一个dom元素
 */
const boxItems = useTemplateRef("boxItem");

/**
 * 是否显示
 * @description
 * 通过比较top和触发点的位置,来判断是否显示
 */
function isShow(top: number, triggerBottom: number) {
	return top < triggerBottom;
}

/**
 * 响应式的计算函数
 * @description
 * 用于实现动态绑定两个数据,用computed动态关联两个响应式数据
 */
const isShowReactive = reactify(isShow);

/**
 * 初始化函数
 * @description
 * 初始化动态的响应式数据群
 * 要在先获取dom的情况下再去初始化响应式数据 应当在onMounted生命周期内执行
 */
function init() {
	const renders = boxItems.value.map((boxItem) => {
		// 获取动态计算的值 该值会随着滚动实现动态计算
		const { top } = useElementBounding(boxItem);

		// 根据响应式数据,动态计算出当前元素是否应该显示出来,后续的样式变更根据此状态完成
		const isShow = isShowReactive(top, triggerBottom);
		return {
			top,
			isShow,
		};
	});
	return renders;
}

/**
 * 记录状态的数组
 * @description
 * 一开始就是空数组
 */
const renders = ref<ReturnType<typeof init>>([]);

onMounted(() => {
	renders.value = init();
});
</script>

<template>
	<section class="vueuse-scroll-animation">
		<div class="box-container">
			<!-- 如果存在状态值,就动态地添加类 -->
			<section class="box" v-for="num in 20" :key="num" ref="boxItem" :class="{ show: renders?.[num - 1]?.isShow }">
				{{ num }}
			</section>
		</div>
	</section>
</template>

<style lang="scss" scoped>
.vueuse-scroll-animation {
	width: 100%;
	// 增加该配置后 动画效果没那么好看了
	// overflow: hidden;

	/* 容器样式:使用 Flex 布局、圆角和阴影 */
	.box-container {
		display: flex;
		flex-wrap: wrap;
		justify-content: center;
		align-items: center;

		.box {
			background-color: steelblue;
			color: #fff;
			display: flex;
			align-items: center;
			justify-content: center;
			width: 400px;
			height: 200px;
			margin: 10px;
			border-radius: 10px;
			box-shadow: 2px 4px 5px rgba(0, 0, 0, 0.3);
			transition: transform 0.4s ease;
			font-size: 4rem;

			// 奇数个方块向右滑入
			&:nth-of-type(odd) {
				transform: translateX(400%);
			}

			// 偶数个方块向左滑入
			&:nth-of-type(even) {
				transform: translateX(-400%);
			}

			/* 添加 show 类时的样式,实现方块滑入效果 */
			&.show {
				transform: translateX(0);
			}
		}
	}
}
</style>

贡献者

The avatar of contributor named as ruan-cat ruan-cat

页面历史