基于Vue開發(fā)一個很火的卡片動畫效果
扣子這個 AI 平臺,可以看到它首頁的卡片效果還是很酷炫的;大致包含兩個效果,光的跟隨效果還有卡片傾斜像 3D
的效果
github
在你沒有登錄的時候,首頁也有這樣一個卡片效果
我們也來實(shí)現(xiàn)一下,寫一個這樣的效果
先準(zhǔ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> ) }
實(shí)現(xiàn)光源跟隨效果
1、需要監(jiān)聽盒子上的 mouseMove 事件和 mouseLeave 事件,進(jìn)入的時候顯示光源并計算隨鼠標(biāo)滾動的位置
2、需要注意光源不能擋住元素上面的位置,所以要設(shè)置低一點(diǎn)的層級
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)移動 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) // 父元素相對于頁面窗口 const { x, y } = cardRef.current.getBoundingClientRect() // 鼠標(biāo)在頁面位置 const { clientX, clientY } = e //光源隨鼠標(biāo)移動 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)'
這個屬性實(shí)現(xiàn)
perspective(1000px)
: 這個函數(shù)定義了元素的透視效果。它接受一個參數(shù),表示視點(diǎn)(觀察者)與屏幕之間的距離。在這個例子中,透視距離被設(shè)置為 1000 像素,使得元素在進(jìn)行 3D 變換時產(chǎn)生透視效果。也就是偏移幅度
。
rotateX(10deg)
: 這個函數(shù)定義了元素繞其 X 軸旋轉(zhuǎn)的角度。它接受一個參數(shù),表示旋轉(zhuǎn)的角度。在這個例子中,元素繞 X 軸順時針旋轉(zhuǎn)了 10 度。
rotateY(10deg)
: 這個函數(shù)定義了元素繞其 Y 軸旋轉(zhuǎn)的角度。它接受一個參數(shù),表示旋轉(zhuǎn)的角度。在這個例子中,元素繞 Y 軸順時針旋轉(zhuǎn)了 10 度。
scale3d(1, 1, 1)
: 這個函數(shù)定義了元素在三個軸上的縮放比例。它接受三個參數(shù),分別表示 X 軸、Y 軸和 Z 軸上的縮放比例。在這個例子中,元素在三個軸上的縮放比例都為 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)移動 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() // 父元素相對于頁面窗口 const { clientX, clientY } = e // 鼠標(biāo)在頁面位置 const offsetX = clientX - x // 計算鼠標(biāo)在盒子內(nèi)的水平偏移量 const offsetY = clientY - y // 計算鼠標(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 軸上的位置計算繞 X 軸的旋轉(zhuǎn)角度 const rotateY = -1 * ((offsetX - rangeX) / rangeX) * maxYRotation // 根據(jù)鼠標(biāo)在 X 軸上的位置計算繞 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)了一個盒子,如果頁面很多盒子的時候怎么辦,所以還是要封裝個Hook
來邏輯復(fù)用
然后統(tǒng)一的光源有點(diǎn)丑,所以設(shè)置個不同的光源
'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() // 父元素相對于頁面窗口 const { clientX, clientY } = e // 鼠標(biāo)在頁面位置 const offsetX = clientX - x // 計算鼠標(biāo)在盒子內(nèi)的水平偏移量 const offsetY = clientY - y // 計算鼠標(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 軸上的位置計算繞 X 軸的旋轉(zhuǎn)角度 const rotateY = -1 * ((offsetX - rangeX) / rangeX) * maxYRotation // 根據(jù)鼠標(biāo)在 X 軸上的位置計算繞 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ā)一個很火的卡片動畫效果的詳細(xì)內(nèi)容,更多關(guān)于Vue卡片動畫效果的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue?perfect-scrollbar(特定框架里使用非該框架定制庫/插件)
這篇文章主要為大家介紹了vue?perfect-scrollbar在特定框架里使用一款并非為該框架定制的庫/插件如何實(shí)現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪<BR>2023-05-05vue2.6.10+vite2開啟template模板動態(tài)編譯的過程
這篇文章主要介紹了vue2.6.10+vite2開啟template模板動態(tài)編譯,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-02-02vue 自定指令生成uuid滾動監(jiān)聽達(dá)到tab表格吸頂效果的代碼
這篇文章主要介紹了vue 自定指令生成uuid滾動監(jiān)聽達(dá)到tab表格吸頂效果,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09Vue中實(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)知識,文中的示例代碼簡潔易懂,具有一定的學(xué)習(xí)價值,感興趣的小伙伴可以跟隨小編一起了解一下2023-11-11Vue3前端與Python(Django)后端接口簡單代碼示例
這篇文章主要介紹了如何使用Django創(chuàng)建項目和應(yīng)用,配置跨域訪問,并編寫視圖和API,同時還講述了如何使用Vue3創(chuàng)建項目,編寫組件調(diào)用后端API,需要的朋友可以參考下2025-01-01教你三分鐘掌握Vue過濾器filters及時間戳轉(zhuǎn)換
這篇文章教你三分鐘掌握Vue過濾器filters及時間戳轉(zhuǎn)換,本文將結(jié)合時間戳轉(zhuǎn)換的例子帶你快速了解filters的用法,需要的朋友可以參考下2023-03-03