包阅导读总结
1. 页面滚动、元素上升、自定义指令、封装组件、动画效果
2. 本文介绍了实现页面滚动时元素缓慢上升效果的两种方式,包括自定义指令和封装为组件,并分别给出了实现代码和页面使用示例。两种方式均可在 SSR 页面中使用。
3.
– 自定义指令方式
– 定义了相关常量和函数,如 `DISTANCE`、`DURATION`、`THRESHOLD_FOR_TRIGGERING_ANIMATION`、`handleIntersection`、`isBelowViewport`、`isInViewport` 等。
– 构建了指令对象 `directive`,在 `inserted` 钩子中处理动画的创建和观察,在 `unbind` 钩子中停止观察。
– 进行了指令的注册和在页面中的使用,并提供了样式处理。
– 封装为组件方式
– 定义了组件 `slideIn`,包含 `props`、`data`、`computed`、`mounted` 和 `methods` 等部分。
– 在 `mounted` 中根据浏览器是否支持 `IntersectionObserver` 来选择创建观察器或监听滚动事件。
– 给出了页面使用组件的示例,并定义了相关样式。
思维导图:
文章地址:https://juejin.cn/post/7401042923490836480
文章来源:juejin.cn
作者:Blue啊
发布时间:2024/8/11 6:24
语言:中文
总字数:1418字
预计阅读时间:6分钟
评分:89分
标签:前端开发,页面滚动,动画效果,JavaScript,用户体验
以下为原文内容
本内容来源于用户推荐转载,旨在分享知识与观点,如有侵权请联系删除 联系邮箱 media@ilingban.com
效果
实现方式
- 自定义指令
- 封装组件
两种方式均可以在SSR页面中使用
方式1:自定义指令实现
import Vue from 'vue';const DISTANCE = 100; const DURATION = 1000; const THRESHOLD_FOR_TRIGGERING_ANIMATION = 0.1; const animationMap = new WeakMap();function handleIntersection(entries, observer) { for (const entry of entries) { if (entry.isIntersecting) { const animation = animationMap.get(entry.target); if (animation) { animation.play(); } else { entry.target.classList.add('active'); } observer.unobserve(entry.target); } }}let ob;if ('IntersectionObserver' in window) { ob = new IntersectionObserver(handleIntersection, { threshold: THRESHOLD_FOR_TRIGGERING_ANIMATION });} else { ob = { observe(el) { el.__onScroll__ = () => { if (isInViewport(el)) { const animation = animationMap.get(el); if (animation) { animation.play(); } else { el.classList.add('active'); } window.removeEventListener('scroll', el.__onScroll__); } }; window.addEventListener('scroll', el.__onScroll__); }, unobserve(el) { if (el.__onScroll__) { window.removeEventListener('scroll', el.__onScroll__); delete el.__onScroll__; } } };}function isBelowViewport(el) { const rect = el.getBoundingClientRect(); return rect.top > window.innerHeight;}function isInViewport(el) { const rect = el.getBoundingClientRect(); return rect.top < window.innerHeight && rect.bottom > 0;}const directive = { name: 'slide-in', inserted(el, binding) { if (!isBelowViewport(el)) { console.log('Element is not below viewport'); return; } const duration = binding.value && binding.value.duration ? binding.value.duration : DURATION; const animationOptions = { duration: duration, easing: binding.value && binding.value.easing ? binding.value.easing : 'ease' }; let animation; if (el.animate) { animation = el.animate([ { transform: `translateY(${DISTANCE}px)`, opacity: 0.5 }, { transform: 'translateY(0)', opacity: 1 } ], animationOptions); animation.pause(); animationMap.set(el, animation); } else { el.classList.add('animate-fallback'); } ob.observe(el); }, unbind(el) { ob.unobserve(el); }};Vue.directive(directive.name, directive);
注册指令
directives/index.js
import './slide-in'
main.js
import './directives'
在页面中使用
<template> <div class="boxs .scroll-container"> <div class="slide-box" v-slide-in="{ duration: 500, easing: 'ease-in-out' }">0 - slide-directives</div> <div class="slide-box" v-slide-in>1 - slide-directives</div> <div class="slide-box" v-slide-in>2 - slide-directives</div> <div v-slide-in>3 - slide-directives</div> <div v-slide-in="{ duration: 500, easing: 'linear' }">4 - slide-directives</div> <div v-slide-in>5 - slide-directives</div> <div v-slide-in="{ duration: 500 }">6 - slide-directives</div> </div></template>
<style lang="scss" scoped>.boxs { div { text-align: center; width: 800px; height: 300px; background-color: #f2f2f2; margin: 0 auto; margin-top: 20px; }}<!-- 兼容性处理(可放到全局style中) -->.animate-fallback { opacity: 0; transform: translateY(100px); transition: transform 1s ease, opacity 1s ease;}.animate-fallback.active { opacity: 1; transform: translateY(0);}@keyframes slideIn { from { opacity: 0; transform: translateY(100px); } to { opacity: 1; transform: translateY(0); }}.animate-fallback-keyframes { opacity: 0; animation: slideIn 1s ease forwards;}</style>
方式2: 封装为组件
<template> <div ref="animatedElement" :style="computedStyle"> <slot></slot> </div></template><script>export default { name: 'slideIn', props: { duration: { // 动画持续时间 type: Number, default: 1000 }, easing: { // 动画缓动效果 type: String, default: 'ease' }, distance: { // 动画距离 type: Number, default: 100 } }, data() { return { hasAnimated: false // 是否已经动画过 } }, computed: { computedStyle() { return { opacity: this.hasAnimated ? 1 : 0, transform: this.hasAnimated ? 'translateY(0)' : `translateY(${this.distance}px)`, transition: `transform ${this.duration}ms ${this.easing}, opacity ${this.duration}ms ${this.easing}` } } }, mounted() { if (typeof window !== 'undefined' && 'IntersectionObserver' in window) { // 检测是否支持IntersectionObserver this.createObserver() // 创建IntersectionObserver } else { // 如果不支持IntersectionObserver,则使用scroll事件来实现动画 this.observeScroll() } }, methods: { createObserver() { const observer = new IntersectionObserver(entries => { // IntersectionObserver回调函数 entries.forEach(entry => { // 遍历每个观察目标 if (entry.isIntersecting && !this.hasAnimated) { // 如果目标进入视口并且没有动画过 this.hasAnimated = true // 标记动画过 observer.unobserve(entry.target) // 停止观察 } }) }, { threshold: 0.1 }) // 观察阈值,表示目标在视口的百分比 observer.observe(this.$refs.animatedElement) // 观察目标 }, observeScroll() { const onScroll = () => { // scroll事件回调函数 if (this.isInViewport(this.$refs.animatedElement) && !this.hasAnimated) { // 如果目标在视口并且没有动画过 this.hasAnimated = true // 标记动画过 window.removeEventListener('scroll', onScroll) // 停止监听scroll事件 } } window.addEventListener('scroll', onScroll) // 监听scroll事件 }, isInViewport(el) { // 判断目标是否在视口 const rect = el.getBoundingClientRect() return rect.top < window.innerHeight && rect.bottom > 0 } }}</script>
页面使用
<div class="text-slide-in-vue"> <slide-comp v-for="(s ,idx) in list" :key="idx"> <p>{{ s.text }} - slide-comp</p> </slide-comp> </div><div class="level-slide"> <slide-comp v-for="(s, idx) in list" :key="idx" :duration="500 * idx + 500"> <p>{{ s.text }} - slide-comp</p> </slide-comp></div><style>.text-slide-in-vue { p { text-align: center; width: 400px; height: 200px; background-color: goldenrod; margin: 0 auto; margin-top: 20px; }}.level-slide { display: flex; align-items: center; justify-content: flex-start; gap: 20px; p { text-align: center; width: 200px; height: 200px; background-color: blueviolet; margin: 0 auto; margin-top: 20px; }}</style>