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

页面历史

最近更新