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

github 在你沒有登錄的時候,首頁也有這樣一個卡片效果

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

先準備三個盒子
這里用的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>
)
}

實現(xiàn)光源跟隨效果
1、需要監(jiān)聽盒子上的 mouseMove 事件和 mouseLeave 事件,進入的時候顯示光源并計算隨鼠標滾動的位置
2、需要注意光源不能擋住元素上面的位置,所以要設置低一點的層級
3、光源的模糊效果可以用filter:blur(100px)實現(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) //是否顯示光源
// 光源隨鼠標移動
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>
)
}

實現(xiàn) 3D 卡片視差效果
1、主要是通過transform:'perspective(1000px) rotateX(10deg) rotateY(10deg) scale3d(1, 1, 1)'這個屬性實現(xiàn)
perspective(1000px): 這個函數(shù)定義了元素的透視效果。它接受一個參數(shù),表示視點(觀察者)與屏幕之間的距離。在這個例子中,透視距離被設置為 1000 像素,使得元素在進行 3D 變換時產(chǎn)生透視效果。也就是偏移幅度。
rotateX(10deg): 這個函數(shù)定義了元素繞其 X 軸旋轉的角度。它接受一個參數(shù),表示旋轉的角度。在這個例子中,元素繞 X 軸順時針旋轉了 10 度。
rotateY(10deg): 這個函數(shù)定義了元素繞其 Y 軸旋轉的角度。它接受一個參數(shù),表示旋轉的角度。在這個例子中,元素繞 Y 軸順時針旋轉了 10 度。
scale3d(1, 1, 1): 這個函數(shù)定義了元素在三個軸上的縮放比例。它接受三個參數(shù),分別表示 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 // 根據(jù)鼠標在 Y 軸上的位置計算繞 X 軸的旋轉角度
const rotateY = -1 * ((offsetX - rangeX) / rangeX) * maxYRotation // 根據(jù)鼠標在 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
聰明的你肯定看到了只實現(xiàn)了一個盒子,如果頁面很多盒子的時候怎么辦,所以還是要封裝個Hook來邏輯復用
然后統(tǒng)一的光源有點丑,所以設置個不同的光源
'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 // 根據(jù)鼠標在 Y 軸上的位置計算繞 X 軸的旋轉角度
const rotateY = -1 * ((offsetX - rangeX) / rangeX) * maxYRotation // 根據(jù)鼠標在 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開發(fā)一個很火的卡片動畫效果的詳細內容,更多關于Vue卡片動畫效果的資料請關注腳本之家其它相關文章!
相關文章
vue?perfect-scrollbar(特定框架里使用非該框架定制庫/插件)
這篇文章主要為大家介紹了vue?perfect-scrollbar在特定框架里使用一款并非為該框架定制的庫/插件如何實現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪<BR>2023-05-05
vue2.6.10+vite2開啟template模板動態(tài)編譯的過程
這篇文章主要介紹了vue2.6.10+vite2開啟template模板動態(tài)編譯,本文結合實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-02-02
vue 自定指令生成uuid滾動監(jiān)聽達到tab表格吸頂效果的代碼
這篇文章主要介紹了vue 自定指令生成uuid滾動監(jiān)聽達到tab表格吸頂效果,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09
詳解vuex中mutations方法的使用與實現(xiàn)
這篇文章主要為大家詳細介紹了vuex中mutations方法的使用與實現(xiàn)的相關知識,文中的示例代碼簡潔易懂,具有一定的學習價值,感興趣的小伙伴可以跟隨小編一起了解一下2023-11-11
Vue3前端與Python(Django)后端接口簡單代碼示例
這篇文章主要介紹了如何使用Django創(chuàng)建項目和應用,配置跨域訪問,并編寫視圖和API,同時還講述了如何使用Vue3創(chuàng)建項目,編寫組件調用后端API,需要的朋友可以參考下2025-01-01

