首页 > 其他分享 >如何实现元素的平滑上升?(vue和react版)

如何实现元素的平滑上升?(vue和react版)

时间:2023-11-14 15:56:08浏览次数:29  
标签:el vue const 动画 平滑 return react animation entry

首先我们看下我们有时候需要在官网或者列表中给元素添加一个动画使元素能够平滑的出现在我们的视野中。

 

如上图所示,我们在vue中可以自定义指令,当我们需要的时候可以直接使用。废话不多说直接上代码。

首先我们创建一个vSlideIn.ts文件

import { DirectiveBinding } from 'vue';
const DISTANCE = 120;
const DURATION = 2000;
const animationMap = new WeakMap(); //不使用map避免内存泄漏
const ob = new IntersectionObserver(entries=>{
    for(const entry of entries){
        if(entry.isIntersecting) {
          // entry.target.getAnimations()[0].play() // 这个方法可以获取所有的动画
           const animation = animationMap.get(entry.target);
           animation.play();
           ob.unobserve(entry.target)
        }
    }
})
​
function isBelowViewport(el:HTMLElement) { //判断是否在视口下方
    const rect = el.getBoundingClientRect();
    return rect.top > window.innerHeight;
}
​
export default {
    mounted(el: HTMLElement, binding: DirectiveBinding) {
        if(!isBelowViewport(el)){
            return;
        }
        const animation = el.animate(
          [
            {
              transform: `translateY(${DISTANCE}px)`,
              opacity: 0.2,
            },
            {
              transform: 'translateY(0)',
              opacity: 1,
            },
          ],
          {
            duration: binding.arg || DURATION,
            easing: 'ease',
          }
        );
        // animation.pause();
        animationMap.set(el,animation)
        ob.observe(el);
    },
    unmounted(el:HTMLElement) { //卸载时候取消监听
        ob.unobserve(el);
    }
}

然后我们在main.ts中注册全局自定义指令
import vSlideIn from './utils/vSlideIn';
const app = createApp(App);
app.directive('slide-in',vSlideIn)

 

然后直接在需要的自元素上使用

<van-list v-if="list.length" v-model:loading="loading" :finished="finished" :finished-text="'NoMore'"
                @load="onLoad">
                <ul>
                    <li v-for="(item, index) in list" :key="index"
                        class="item" v-slide-in>
                        <div class="d-flex justify-content-between">
                        </div>
                    </li>
                </ul>
  </van-list>
<Empty v-else/>

 

就能直接看到上面的效果了,距离和动画时间可以自己定义合适的就可以了。

react版本

下面是一个基础改写的react版本

import React, { useEffect, useRef } from 'react';
​
const DISTANCE = 80;
const DURATION = 1000;
const animationMap = new WeakMap(); // 避免内存泄漏
const ob = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    if (entry.isIntersecting) {
      const animation = animationMap.get(entry.target);
      animation.play();
      ob.unobserve(entry.target);
    }
  }
});
​
function isBelowViewport(el) {
  const rect = el.getBoundingClientRect();
  return rect.top > window.innerHeight;
}
​
export default function CustomDirective({ children }) {
  const elementRef = useRef(null);
​
  useEffect(() => {
    const el = elementRef.current;
​
    if (!el || !isBelowViewport(el)) {
      return;
    }
​
    const animation = el.animate(
      [
        {
          transform: `translateY(${DISTANCE}px)`,
          opacity: 0.5,
        },
        {
          transform: 'translateY(0)',
          opacity: 1,
        },
      ],
      {
        duration: DURATION,
        easing: 'ease',
      }
    );
​
    animationMap.set(el, animation);
    ob.observe(el);
​
    return () => {
      ob.unobserve(el);
    };
  }, []);
​
  return <div ref={elementRef}>{children}</div>;
}

 

在上述代码中,我们使用了 useEffect 钩子来模拟 Vue 的 mountedunmounted 钩子函数。useEffect 的第二个参数为空数组 [],表示只在组件首次渲染时执行一次。

我们使用 useRef 创建了一个引用 elementRef,用于获取组件根元素的 DOM 节点。然后,我们在 useEffect 中使用这个引用来操作 DOM 元素和创建动画。

当组件首次渲染时,我们检查元素是否位于视口下方,如果是,则创建动画效果,并将元素添加到 IntersectionObserver 中进行观察。当元素进入视口后,播放动画并停止观察。

你可以将 CustomDirective 组件应用到任意需要动画效果的 React 元素中,如下所示:

import React from 'react';
import CustomDirective from './CustomDirective';
function App() {
  return (
    <div>
      <h1>React 自定义指令示例</h1>
      <CustomDirective>
        <p>具备动画效果的元素</p>
      </CustomDirective>
    </div>
  );
}

 

上述的代码还可以继续优化

下面是一些可能的优化方案:

  1. 使用 useCallback 优化回调函数:在 useEffect 中使用的回调函数可以使用 useCallback 进行优化,以避免在每次渲染时重新创建回调函数。

  2. 使用 useMemo 优化动画对象的存储:可以使用 useMemo 来存储动画对象,以避免在每次渲染时重新创建动画对象。

  3. 使用 useRef 优化 Intersection Observer 的初始化:可以使用 useRef 来存储 Intersection Observer 对象,并在初始化时进行一次性的创建和绑定。

下面是经过优化的代码示例:

import React, { useEffect, useRef, useCallback, useMemo } from 'react';
​
const DISTANCE = 80;
const DURATION = 1000;
​
function isBelowViewport(el) {
  const rect = el.getBoundingClientRect();
  return rect.top > window.innerHeight;
}
​
export default function CustomDirective({ children }) {
  const elementRef = useRef(null);
  const animationRef = useRef(null);
  const observerRef = useRef(null);
​
  const handleIntersection = useCallback(
    (entries) => {
      for (const entry of entries) {
        if (entry.isIntersecting) {
          const animation = animationRef.current;
          animation.play();
          observerRef.current.unobserve(entry.target);
        }
      }
    },
    []
  );
​
  useEffect(() => {
    const el = elementRef.current;
​
    if (!el || !isBelowViewport(el)) {
      return;
    }
​
    const animation = el.animate(
      [
        {
          transform: `translateY(${DISTANCE}px)`,
          opacity: 0.5,
        },
        {
          transform: 'translateY(0)',
          opacity: 1,
        },
      ],
      {
        duration: DURATION,
        easing: 'ease',
      }
    );
​
    animationRef.current = animation;
    observerRef.current = new IntersectionObserver(handleIntersection);
​
    observerRef.current.observe(el);
​
    return () => {
      observerRef.current.unobserve(el);
    };
  }, [handleIntersection]);
​
  const animatedElement = useMemo(() => <div ref={elementRef}>{children}</div>, [children]);
​
  return animatedElement;
}

使用的话和上面的一样直接引入当组件就可以了。

当然了这个动画还可以引入很多现有的库,也很方便简单,但是我们自定义的话动画的内容可以更可控一些。

标签:el,vue,const,动画,平滑,return,react,animation,entry
From: https://www.cnblogs.com/ximenchuifa/p/17831804.html

相关文章

  • Vue3调用Element-plus涉及子组件v-model双向绑定props问题
    Vue3调用Element-plus涉及子组件v-model双向绑定props问题在Vue3调用Element-plus的el-dialog组件时,碰到个很有意思的问题,el-dialog的属性值v-model直接控制对话框的显示与否,点击关闭对话框和遮罩区域,组件内部会自动更改v-model的值为false来关闭对话框。问题在于当组件作为子组......
  • vuejs3.0 从入门到精通——Pinia——定义Store
    定义Store Store是用defineStore()定义的,它的第一个参数要求是一个独一无二的名字:import{defineStore}from'pinia'//你可以对`defineStore()`的返回值进行任意命名,但最好使用store的名字,同时以`use`开头且以`Store`结尾。(比如`useUserStore`,`useCartStore......
  • 在vue项目开发过程中,输入框以表单形式提交后,路径中多了问号?
    结果是:http://localhost:8100/#/  改变为  http://localhost:8100/?#/  导致路由跳转出现问题。 原因:这里是form表单,点击了button按钮,触发了他的默认事件,就是触发了提交这个行为。 解决方案:使用@click.prevent阻止默认事件 <a-buttontype="primary"@click.pr......
  • js:React中使用classnames实现按照条件将类名连接起来
    参考文档https://www.npmjs.com/package/classnameshttps://github.com/JedWatson/classnames安装npminstallclassnames示例importclassNamesfrom"classnames";//字符串合并console.log(classNames("foo","bar"));//foobar//对象合并console.lo......
  • 基于React使用swiperjs实现竖向滚动自动轮播
    很多文章,都只提供了js部分,包括官方的文档也只有js部分,如果css设置不正确,会导致轮播图不自动播放。使用的swiper版本:v11.0.3文档https://swiperjs.com/get-startedhttps://swiperjs.com/react实现效果使用vite创建react应用pnpmcreatevitereact-app--templatereact完整依赖pac......
  • react| 封装TimeLine组件
    功能支持居中/局左/居右布局可自定义线条颜色默认情况下图标是圆形,可自定义圆形颜色和大小,同时也可以自定义图标支持自定义内容效果constdata=[{"title":"2022-12-0512:03:40","des":"茶陵县实时广播防火宣传"},...]<TimeLineda......
  • vuejs3.0 从入门到精通——Pinia——Store 是什么?
    Pinia——Store是什么?https://pinia.vuejs.org/zh/getting-started.html#what-is-a-store一、Store是什么? Store(如Pinia)是一个保存状态和业务逻辑的实体,它并不与你的组件树绑定。换句话说,它承载着全局状态。它有点像一个永远存在的组件,每个组件都可以读取和写入它。......
  • 深入理解 Vuex
    Vue.js是一款轻量级、灵活且易学的前端框架,但随着应用的复杂性增加,组件之间的状态管理变得愈发复杂。为了解决这一问题,Vue.js提供了一个强大的状态管理工具——Vuex。在本篇博客中,我们将深入探讨Vuex的核心概念和使用方法,并通过实例演示如何优雅地管理应用状态。什么是Vuex?Vue......
  • Vue3实现图片滚轮缩放和拖拽
    在项目开发中遇到一个需求:1:用鼠标滚轮可对图片进行缩放处理2:点击按钮可对图片进行缩放处理3:可对图片进行拖拽处理 我在开发中通过自己实现与百度查看优秀的铁子进行了两种类型的使用  <template><divref="imgWrap"class="wrap"@mousewheel.prevent="rollImg"......
  • vue初始
    简介Vue的两个核心功能:声明式渲染:Vue基于标准HTML拓展了一套模板语法,使得我们可以声明式地描述最终输出的HTML和JavaScript状态之间的关系。响应性:Vue会自动跟踪JavaScript状态并在其发生变化时响应式地更新DOM。API风格Vue的组件可以按两种不同的风......