基于Vue開發(fā)一個(gè)很火的卡片動(dòng)畫效果
扣子這個(gè) AI 平臺(tái),可以看到它首頁的卡片效果還是很酷炫的;大致包含兩個(gè)效果,光的跟隨效果還有卡片傾斜像 3D 的效果

github 在你沒有登錄的時(shí)候,首頁也有這樣一個(gè)卡片效果

我們也來實(shí)現(xiàn)一下,寫一個(gè)這樣的效果

先準(zhǔn)備三個(gè)盒子
這里用的react組件和tailwind來寫樣式,盒子不管你怎么寫,但是得有 relative 定位,因?yàn)楣庑枰枚ㄎ粊砀S
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>
)
}

實(shí)現(xiàn)光源跟隨效果
1、需要監(jiān)聽盒子上的 mouseMove 事件和 mouseLeave 事件,進(jìn)入的時(shí)候顯示光源并計(jì)算隨鼠標(biāo)滾動(dòng)的位置
2、需要注意光源不能擋住元素上面的位置,所以要設(shè)置低一點(diǎn)的層級(jí)
3、光源的模糊效果可以用filter:blur(100px)實(shí)現(xiàn)
'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) //是否顯示光源
// 光源隨鼠標(biāo)移動(dòng)
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) {
// 進(jìn)入盒子顯示光源
setIsShowLight(true)
// 父元素相對(duì)于頁面窗口
const { x, y } = cardRef.current.getBoundingClientRect()
// 鼠標(biāo)在頁面位置
const { clientX, clientY } = e
//光源隨鼠標(biāo)移動(dòng)
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>
)
}

實(shí)現(xiàn) 3D 卡片視差效果
1、主要是通過transform:'perspective(1000px) rotateX(10deg) rotateY(10deg) scale3d(1, 1, 1)'這個(gè)屬性實(shí)現(xiàn)
perspective(1000px): 這個(gè)函數(shù)定義了元素的透視效果。它接受一個(gè)參數(shù),表示視點(diǎn)(觀察者)與屏幕之間的距離。在這個(gè)例子中,透視距離被設(shè)置為 1000 像素,使得元素在進(jìn)行 3D 變換時(shí)產(chǎn)生透視效果。也就是偏移幅度。
rotateX(10deg): 這個(gè)函數(shù)定義了元素繞其 X 軸旋轉(zhuǎn)的角度。它接受一個(gè)參數(shù),表示旋轉(zhuǎn)的角度。在這個(gè)例子中,元素繞 X 軸順時(shí)針旋轉(zhuǎn)了 10 度。
rotateY(10deg): 這個(gè)函數(shù)定義了元素繞其 Y 軸旋轉(zhuǎn)的角度。它接受一個(gè)參數(shù),表示旋轉(zhuǎn)的角度。在這個(gè)例子中,元素繞 Y 軸順時(shí)針旋轉(zhuǎn)了 10 度。
scale3d(1, 1, 1): 這個(gè)函數(shù)定義了元素在三個(gè)軸上的縮放比例。它接受三個(gè)參數(shù),分別表示 X 軸、Y 軸和 Z 軸上的縮放比例。在這個(gè)例子中,元素在三個(gè)軸上的縮放比例都為 1,表示不進(jìn)行縮放。
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) //是否顯示光源
// 光源隨鼠標(biāo)移動(dòng)
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) // 進(jìn)入盒子顯示光源
const { x, y } = cardRef.current.getBoundingClientRect() // 父元素相對(duì)于頁面窗口
const { clientX, clientY } = e // 鼠標(biāo)在頁面位置
const offsetX = clientX - x // 計(jì)算鼠標(biāo)在盒子內(nèi)的水平偏移量
const offsetY = clientY - y // 計(jì)算鼠標(biāo)在盒子內(nèi)的垂直偏移量
setPos({
left: offsetX - 100 + 'px', // 100為光源寬度的1/2
top: offsetY - 100 + 'px' // 100為光源高度的1/2
})
// 新增
const maxXRotation = 10 // 最大繞 X 軸旋轉(zhuǎn)角度
const maxYRotation = 10 // 最大繞 Y 軸旋轉(zhuǎn)角度
const rangeX = 400 / 2 // X 軸旋轉(zhuǎn)范圍
const rangeY = 400 / 2 // Y 軸旋轉(zhuǎn)范圍
const rotateX = ((offsetY - rangeY) / rangeY) * maxXRotation // 根據(jù)鼠標(biāo)在 Y 軸上的位置計(jì)算繞 X 軸的旋轉(zhuǎn)角度
const rotateY = -1 * ((offsetX - rangeX) / rangeX) * maxYRotation // 根據(jù)鼠標(biāo)在 X 軸上的位置計(jì)算繞 Y 軸的旋轉(zhuǎn)角度
cardRef.current.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)` //設(shè)置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
聰明的你肯定看到了只實(shí)現(xiàn)了一個(gè)盒子,如果頁面很多盒子的時(shí)候怎么辦,所以還是要封裝個(gè)Hook來邏輯復(fù)用
然后統(tǒng)一的光源有點(diǎn)丑,所以設(shè)置個(gè)不同的光源
'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) // 進(jìn)入盒子顯示光源
const { x, y } = cardRef.current.getBoundingClientRect() // 父元素相對(duì)于頁面窗口
const { clientX, clientY } = e // 鼠標(biāo)在頁面位置
const offsetX = clientX - x // 計(jì)算鼠標(biāo)在盒子內(nèi)的水平偏移量
const offsetY = clientY - y // 計(jì)算鼠標(biāo)在盒子內(nèi)的垂直偏移量
setPos({
left: offsetX - 100 + 'px', // 100為光源寬度的1/2
top: offsetY - 100 + 'px' // 100為光源高度的1/2
})
const maxXRotation = 5 // 最大繞 X 軸旋轉(zhuǎn)角度
const maxYRotation = 5 // 最大繞 Y 軸旋轉(zhuǎn)角度
const rangeX = 400 / 2 // X 軸旋轉(zhuǎn)范圍
const rangeY = 400 / 2 // Y 軸旋轉(zhuǎn)范圍
const rotateX = ((offsetY - rangeY) / rangeY) * maxXRotation // 根據(jù)鼠標(biāo)在 Y 軸上的位置計(jì)算繞 X 軸的旋轉(zhuǎn)角度
const rotateY = -1 * ((offsetX - rangeX) / rangeX) * maxYRotation // 根據(jù)鼠標(biāo)在 X 軸上的位置計(jì)算繞 Y 軸的旋轉(zhuǎn)角度
cardRef.current.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)` // 設(shè)置3D透視
}
}
const handleMouseLeave = () => {
setIsShowLight(false) // 離開盒子隱藏光源
if (cardRef.current) {
cardRef.current.style.transform = `perspective(1000px) rotateX(0deg) rotateY(0deg)` // 設(shè)置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開發(fā)一個(gè)很火的卡片動(dòng)畫效果的詳細(xì)內(nèi)容,更多關(guān)于Vue卡片動(dòng)畫效果的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue?perfect-scrollbar(特定框架里使用非該框架定制庫/插件)
這篇文章主要為大家介紹了vue?perfect-scrollbar在特定框架里使用一款并非為該框架定制的庫/插件如何實(shí)現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪<BR>2023-05-05
vue2.6.10+vite2開啟template模板動(dòng)態(tài)編譯的過程
這篇文章主要介紹了vue2.6.10+vite2開啟template模板動(dòng)態(tài)編譯,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-02-02
vue 自定指令生成uuid滾動(dòng)監(jiān)聽達(dá)到tab表格吸頂效果的代碼
這篇文章主要介紹了vue 自定指令生成uuid滾動(dòng)監(jiān)聽達(dá)到tab表格吸頂效果,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09
Vue中實(shí)現(xiàn)路由跳轉(zhuǎn)的三種方式(超詳細(xì)整理)
這篇文章給大家詳細(xì)的整理了Vue中實(shí)現(xiàn)路由跳轉(zhuǎn)的三種方式,使用vue-router,聲明式-router-link,編程式這三種方法,分別有詳細(xì)的代碼示例,需要的朋友可以參考下2023-09-09
詳解vuex中mutations方法的使用與實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了vuex中mutations方法的使用與實(shí)現(xiàn)的相關(guān)知識(shí),文中的示例代碼簡潔易懂,具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以跟隨小編一起了解一下2023-11-11
element step組件在另一側(cè)加時(shí)間軸顯示
本文主要介紹了element step組件在另一側(cè)加時(shí)間軸顯示,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06
動(dòng)畫詳解Vue3的Composition?Api
為讓大家更好的理解Vue3的Composition?Api本文采用了詳細(xì)的動(dòng)畫演繹,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07
Vue3前端與Python(Django)后端接口簡單代碼示例
這篇文章主要介紹了如何使用Django創(chuàng)建項(xiàng)目和應(yīng)用,配置跨域訪問,并編寫視圖和API,同時(shí)還講述了如何使用Vue3創(chuàng)建項(xiàng)目,編寫組件調(diào)用后端API,需要的朋友可以參考下2025-01-01
教你三分鐘掌握Vue過濾器filters及時(shí)間戳轉(zhuǎn)換
這篇文章教你三分鐘掌握Vue過濾器filters及時(shí)間戳轉(zhuǎn)換,本文將結(jié)合時(shí)間戳轉(zhuǎn)換的例子帶你快速了解filters的用法,需要的朋友可以參考下2023-03-03

