Posted in

前端实现:页面滚动时,元素缓慢上升效果_AI阅读总结 — 包阅AI

包阅导读总结

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

效果

2024-08-11 13.58.04.gif

实现方式

  1. 自定义指令
  2. 封装组件

两种方式均可以在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);

注册指令

image.png

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>