基于Vue开发一个很火的卡片动画效果

 更新时间:2024年02月06日 08:17:49   作者:imber  
这篇文章主要为大家详细介绍了如何基于Vue开发一个很火的卡片动画效果,大致包含两个效果,光的跟随效果还有卡片倾斜像 3D 的效果,感兴趣的可以了解一下

扣子这个 AI 平台,可以看到它首页的卡片效果还是很酷炫的;大致包含两个效果,光的跟随效果还有卡片倾斜像 3D 的效果

github 在你没有登录的时候,首页也有这样一个卡片效果

我们也来实现一下,写一个这样的效果

先准备三个盒子

这里用的react组件和tailwind来写样式,盒子不管你怎么写,但是得有 relative 定位,因为光需要用定位来跟随

export default function Home() {
  return (
    <main className="min-h-screen p-24 flex justify-center items-center bg-black gap-5">
      <div className="w-[384px] h-[384px] flex-center flex-col rounded-lg border border-[rgba(255,255,255,0.1)] bg-[#1C1C1F]"></div>
      <div className="w-[384px] h-[384px] flex-center flex-col rounded-lg border border-[rgba(255,255,255,0.1)] bg-[#1C1C1F]"></div>
      <div className="w-[384px] h-[384px] flex-center flex-col rounded-lg border border-[rgba(255,255,255,0.1)] bg-[#1C1C1F]"></div>
    </main>
  )
}

实现光源跟随效果

1、需要监听盒子上的 mouseMove 事件和 mouseLeave 事件,进入的时候显示光源并计算随鼠标滚动的位置

2、需要注意光源不能挡住元素上面的位置,所以要设置低一点的层级

3、光源的模糊效果可以用filter:blur(100px)实现

'use client'
import { useRef, useState } from 'react'

export default function Home() {
  const cardRef = (useRef < HTMLDivElement) | (null > null) //卡片
  const lightRef = (useRef < HTMLDivElement) | (null > null) //光源
  const [isShowLight, setIsShowLight] = useState(false) //是否显示光源
  // 光源随鼠标移动
  const [pos, setPos] = useState({
    left: '0px',
    top: '0px'
  })

  return (
    <main className="h-screen p-24 flex justify-center items-center bg-black gap-5">
      <div
        className="w-[384px] h-[384px] flex-center flex-col rounded-lg border border-[rgba(255,255,255,0.1)] bg-[#1C1C1F] relative overflow-hidden"
        onMouseMove={(e: React.MouseEvent<HTMLDivElement>) => {
          if (cardRef.current) {
            // 进入盒子显示光源
            setIsShowLight(true)
            // 父元素相对于页面窗口
            const { x, y } = cardRef.current.getBoundingClientRect()
            // 鼠标在页面位置
            const { clientX, clientY } = e
            //光源随鼠标移动
            setPos({
              left: clientX - x - 100 + 'px', // 100为光源宽度的1/2
              top: clientY - y - 100 + 'px' // 100为光源高度的1/2
            })
          }
        }}
        onMouseLeave={() => {
          // 离开盒子隐藏光源
          setIsShowLight(false)
        }}
        ref={cardRef}
      >
        <div
          className={`${
            isShowLight ? '' : 'hidden'
          } absolute h-[200px] w-[200px] rounded-full bg-[#ff4132] blur-[150px] filter`}
          ref={lightRef}
          style={pos}
        ></div>
      </div>
      <div className="w-[384px] h-[384px] flex-center flex-col rounded-lg border border-[rgba(255,255,255,0.1)] bg-[#1C1C1F] relative"></div>
      <div className="w-[384px] h-[384px] flex-center flex-col rounded-lg border border-[rgba(255,255,255,0.1)] bg-[#1C1C1F] relative"></div>
    </main>
  )
}

实现 3D 卡片视差效果

1、主要是通过transform:'perspective(1000px) rotateX(10deg) rotateY(10deg) scale3d(1, 1, 1)'这个属性实现

perspective(1000px): 这个函数定义了元素的透视效果。它接受一个参数,表示视点(观察者)与屏幕之间的距离。在这个例子中,透视距离被设置为 1000 像素,使得元素在进行 3D 变换时产生透视效果。也就是偏移幅度

rotateX(10deg): 这个函数定义了元素绕其 X 轴旋转的角度。它接受一个参数,表示旋转的角度。在这个例子中,元素绕 X 轴顺时针旋转了 10 度。

rotateY(10deg): 这个函数定义了元素绕其 Y 轴旋转的角度。它接受一个参数,表示旋转的角度。在这个例子中,元素绕 Y 轴顺时针旋转了 10 度。

scale3d(1, 1, 1): 这个函数定义了元素在三个轴上的缩放比例。它接受三个参数,分别表示 X 轴、Y 轴和 Z 轴上的缩放比例。在这个例子中,元素在三个轴上的缩放比例都为 1,表示不进行缩放。

import { useRef, useState } from 'react'

export default function Home() {
  const cardRef = (useRef < HTMLDivElement) | (null > null) //卡片
  const lightRef = (useRef < HTMLDivElement) | (null > null) //光源
  const [isShowLight, setIsShowLight] = useState(false) //是否显示光源
  // 光源随鼠标移动
  const [pos, setPos] = useState({
    left: '0px',
    top: '0px'
  })

  return (
    <main className="h-screen p-24 flex justify-center items-center bg-black gap-5">
      <div
        className="w-[400px] h-[400px] flex-center flex-col rounded-lg border border-[rgba(255,255,255,0.1)] bg-[#1C1C1F] relative overflow-hidden"
        onMouseMove={(e: React.MouseEvent<HTMLDivElement>) => {
          if (cardRef.current) {
            setIsShowLight(true) // 进入盒子显示光源

            const { x, y } = cardRef.current.getBoundingClientRect() // 父元素相对于页面窗口
            const { clientX, clientY } = e // 鼠标在页面位置

            const offsetX = clientX - x // 计算鼠标在盒子内的水平偏移量
            const offsetY = clientY - y // 计算鼠标在盒子内的垂直偏移量

            setPos({
              left: offsetX - 100 + 'px', // 100为光源宽度的1/2
              top: offsetY - 100 + 'px' // 100为光源高度的1/2
            })

            // 新增

            const maxXRotation = 10 // 最大绕 X 轴旋转角度
            const maxYRotation = 10 // 最大绕 Y 轴旋转角度

            const rangeX = 400 / 2 // X 轴旋转范围
            const rangeY = 400 / 2 // Y 轴旋转范围

            const rotateX = ((offsetY - rangeY) / rangeY) * maxXRotation // 根据鼠标在 Y 轴上的位置计算绕 X 轴的旋转角度
            const rotateY = -1 * ((offsetX - rangeX) / rangeX) * maxYRotation // 根据鼠标在 X 轴上的位置计算绕 Y 轴的旋转角度

            cardRef.current.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)` //设置3D透视
          }
        }}
        onMouseLeave={() => {
          // 离开盒子隐藏光源
          setIsShowLight(false)
        }}
        ref={cardRef}
        style={{
          willChange: 'transform',
          transform:
            'perspective(1000px) rotateX(0deg) rotateY(0deg) scale3d(1, 1, 1)'
        }}
      >
        <div
          className={`${
            isShowLight ? '' : 'hidden'
          } absolute h-[200px] w-[200px] rounded-full bg-[#ff4132] blur-[150px] filter`}
          ref={lightRef}
          style={pos}
        ></div>
      </div>
      <div className="w-[400px] h-[400px] flex-center flex-col rounded-lg border border-[rgba(255,255,255,0.1)] bg-[#1C1C1F] relative"></div>
      <div className="w-[400px] h-[400px] flex-center flex-col rounded-lg border border-[rgba(255,255,255,0.1)] bg-[#1C1C1F] relative"></div>
    </main>
  )
}

封装成好用的 Hook

聪明的你肯定看到了只实现了一个盒子,如果页面很多盒子的时候怎么办,所以还是要封装个Hook来逻辑复用

然后统一的光源有点丑,所以设置个不同的光源

'use client'

import { useRef, useState, useEffect } from 'react'

const useCardAnimation = () => {
  const cardRef = (useRef < HTMLDivElement) | (null > null) // 卡片
  const lightRef = (useRef < HTMLDivElement) | (null > null) // 光源
  const [isShowLight, setIsShowLight] = useState(false) // 是否显示光源
  const [pos, setPos] = useState({ left: '0px', top: '0px' }) // 光源位置

  useEffect(() => {
    const handleMouseMove = (e: MouseEvent) => {
      if (cardRef.current) {
        setIsShowLight(true) // 进入盒子显示光源

        const { x, y } = cardRef.current.getBoundingClientRect() // 父元素相对于页面窗口
        const { clientX, clientY } = e // 鼠标在页面位置

        const offsetX = clientX - x // 计算鼠标在盒子内的水平偏移量
        const offsetY = clientY - y // 计算鼠标在盒子内的垂直偏移量

        setPos({
          left: offsetX - 100 + 'px', // 100为光源宽度的1/2
          top: offsetY - 100 + 'px' // 100为光源高度的1/2
        })

        const maxXRotation = 5 // 最大绕 X 轴旋转角度
        const maxYRotation = 5 // 最大绕 Y 轴旋转角度

        const rangeX = 400 / 2 // X 轴旋转范围
        const rangeY = 400 / 2 // Y 轴旋转范围

        const rotateX = ((offsetY - rangeY) / rangeY) * maxXRotation // 根据鼠标在 Y 轴上的位置计算绕 X 轴的旋转角度
        const rotateY = -1 * ((offsetX - rangeX) / rangeX) * maxYRotation // 根据鼠标在 X 轴上的位置计算绕 Y 轴的旋转角度

        cardRef.current.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)` // 设置3D透视
      }
    }

    const handleMouseLeave = () => {
      setIsShowLight(false) // 离开盒子隐藏光源
      if (cardRef.current) {
        cardRef.current.style.transform = `perspective(1000px) rotateX(0deg) rotateY(0deg)` // 设置3D透视
      }
    }

    cardRef.current?.addEventListener('mousemove', handleMouseMove)
    cardRef.current?.addEventListener('mouseleave', handleMouseLeave)

    return () => {
      cardRef.current?.removeEventListener('mousemove', handleMouseMove)
      cardRef.current?.removeEventListener('mouseleave', handleMouseLeave)
    }
  }, [])

  return { cardRef, lightRef, isShowLight, pos }
}

export default function Home() {
  const {
    cardRef: cardRef1,
    lightRef: lightRef1,
    isShowLight: isShowLight1,
    pos: pos1
  } = useCardAnimation()
  const {
    cardRef: cardRef2,
    lightRef: lightRef2,
    isShowLight: isShowLight2,
    pos: pos2
  } = useCardAnimation()
  const {
    cardRef: cardRef3,
    lightRef: lightRef3,
    isShowLight: isShowLight3,
    pos: pos3
  } = useCardAnimation()

  return (
    <main className="h-screen p-24 flex justify-center items-center bg-black gap-5">
      <div
        className="w-[400px] h-[400px] flex-center flex-col rounded-lg border border-[rgba(255,255,255,0.1)] bg-[#1C1C1F] relative overflow-hidden"
        ref={cardRef1}
        style={{
          willChange: 'transform',
          transform:
            'perspective(1000px) rotateX(0deg) rotateY(0deg) scale3d(1, 1, 1)'
        }}
      >
        <div
          className={`${
            isShowLight1 ? '' : 'hidden'
          } absolute h-[200px] w-[200px] rounded-full bg-[#ff4132] blur-[150px] filter`}
          ref={lightRef1}
          style={pos1}
        ></div>
      </div>
      <div
        className="w-[400px] h-[400px] flex-center flex-col rounded-lg border border-[rgba(255,255,255,0.1)] bg-[#1C1C1F] relative overflow-hidden"
        ref={cardRef2}
        style={{
          willChange: 'transform',
          transform:
            'perspective(1000px) rotateX(0deg) rotateY(0deg) scale3d(1, 1, 1)'
        }}
      >
        <div
          className={`${
            isShowLight2 ? '' : 'hidden'
          } absolute h-[200px] w-[200px] rounded-full bg-[#f9b613] blur-[150px] filter`}
          ref={lightRef2}
          style={pos2}
        ></div>
      </div>
      <div
        className="w-[400px] h-[400px] flex-center flex-col rounded-lg border border-[rgba(255,255,255,0.1)] bg-[#1C1C1F] relative overflow-hidden"
        ref={cardRef3}
        style={{
          willChange: 'transform',
          transform:
            'perspective(1000px) rotateX(0deg) rotateY(0deg) scale3d(1, 1, 1)'
        }}
      >
        <div
          className={`${
            isShowLight3 ? '' : 'hidden'
          } absolute h-[200px] w-[200px] rounded-full bg-[#3191f7] blur-[150px] filter`}
          ref={lightRef3}
          style={pos3}
        ></div>
      </div>
    </main>
  )
}

以上就是基于Vue开发一个很火的卡片动画效果的详细内容,更多关于Vue卡片动画效果的资料请关注脚本之家其它相关文章!

相关文章

  • Vue中data数据初始化方法详解

    Vue中data数据初始化方法详解

    这篇文章主要介绍了Vue中data数据初始化方法,数据初始化是在组件实例化时发生的,在组件中,可以通过data选项来定义组件的初始数据,需要详细了解可以参考下文
    2023-05-05
  • vue3+vite使用postcss-pxtorem、autoprefixer自适应和自动添加前缀

    vue3+vite使用postcss-pxtorem、autoprefixer自适应和自动添加前缀

    这篇文章主要介绍了vue3+vite使用postcss-pxtorem、autoprefixer自适应和自动添加前缀方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-10-10
  • 新手简单了解vue

    新手简单了解vue

    Vue.js是一个构建数据驱动的 web 界面的渐进式框架。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。下面我们来简单了解下吧
    2019-05-05
  • 基于vue3实现一个简单的输入框效果

    基于vue3实现一个简单的输入框效果

    这篇文章主要为大家详细介绍了如何使用Vue3实现一个简单的输入框,可以实现输入文字,添加表情等功能,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-03-03
  • 关于vue.js中this.$emit的理解使用

    关于vue.js中this.$emit的理解使用

    本文主要介绍了关于vue.js中this.$emit的理解使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-08-08
  • Vue v-for循环之@click点击事件获取元素示例

    Vue v-for循环之@click点击事件获取元素示例

    今天小编就为大家分享一篇Vue v-for循环之@click点击事件获取元素示例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-11-11
  • vue实现底部弹窗多选

    vue实现底部弹窗多选

    这篇文章主要为大家详细介绍了vue实现底部弹窗多选,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-09-09
  • vue3+elementPlus项目支持生成、设置默认附件方式

    vue3+elementPlus项目支持生成、设置默认附件方式

    这篇文章主要介绍了vue3+elementPlus项目支持生成、设置默认附件方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-03-03
  • vue3.0项目小白填坑之vue3.0+vite获取环境变量

    vue3.0项目小白填坑之vue3.0+vite获取环境变量

    Vue3项目使用vite作为打包工具时,环境变量可以保存在.env文件中,在build时进行解析,这篇文章主要给大家介绍了关于vue3.0项目小白填坑之vue3.0+vite获取环境变量的相关资料,需要的朋友可以参考下
    2024-03-03
  • Vue预览图片和视频的几种实现方式

    Vue预览图片和视频的几种实现方式

    本文主要介绍了Vue预览图片和视频的几种实现方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07

最新评论