模擬面試、簡歷指導、入職指導、計畫指導、答疑解惑 可私信找我~已幫助100+名同學完成改造!
前言
大家好,我是林三心,用最通俗易懂的話講最難的知識點是我的座右銘,基礎是進階的前提是我的初心~
有趣的動畫效果
前幾天在網上看到了一個很有趣的動畫效果,如下,光會跟隨滑鼠在卡片上進行移動,並且卡片會有視差的效果
那麽在 Vue3 中應該如何去實作這個效果呢?
基本實作思路
其實實作思路很簡單,無非就是分幾步:
首先,卡片是
相對定位
,光是
絕對定位
監聽卡片的滑鼠移入事件
mouseenter
,當滑鼠進入時顯示光
監聽卡片的滑鼠移動事件
mouseover
,滑鼠移動時修改光的
left、top
,讓光跟隨滑鼠移動
監聽卡片的滑鼠移出事件
mouseleave
,滑鼠移出時,隱藏光
我們先在
Index.vue
中準備一個卡片頁面,光的CSS效果可以使用
filter: blur()
來實作
可以看到現在的效果是這樣
實作光源跟隨滑鼠
在實作之前我們需要註意幾點:
1、滑鼠移入時需要設定卡片
overflow: hidden
,否則光會溢位,而滑鼠移出時記得還原
2、獲取滑鼠座標時需要用
clientX/Y
而不是
pageX/Y
,因為前者會把頁面捲動距離也算進去,比較嚴謹
剛剛說到實作思路時我們說到了
mouseenter、mousemove、mouseleave
,其實
mouseenter、mouseleave
這二者的邏輯比較簡單,重點是
mouseover
這個監聽函式
而在
mouseover
這個函式中,最重要的邏輯就是:
光怎麽跟隨滑鼠移動呢?
或者也可以這麽說: 怎麽計算光相對於卡片盒子的 left 和 top
對此我專門畫了一張圖,相信大家一看就懂怎麽算了
left = clientX - x - width/2
height = clientY - y - height/2
知道了怎麽計算,那麽邏輯的實作也很明了了~封裝一個
use-light-card.ts
接著在頁面中去使用
這樣就能實作基本的效果啦~
卡片視差效果
卡片的視差效果需要用到樣式中
transform
樣式,主要是配置四個東西:
perspective:定義元素在 3D 變換時的透視效果
rotateX:X 軸旋轉角度
rotateY:Y 軸旋轉角度
scale3d:X/Y/Z 軸上的縮放比例
現在就有了卡片視差的效果啦~
給所有卡片添加光源
上面只是給一個卡片增加光源,接下來可以給每一個卡片都增加光源啦!!!
讓光源變成可配置
上面的程式碼,總感覺這個 hooks 耦合度太高不太通用,所以我們可以讓光源變成可配置化,這樣每個卡片就可以展示不同大小、顏色的光源了~像下面一樣
既然是配置化,那我們希望是這麽去使用 hooks 的,我們並不需要自己在頁面中去寫光源的dom節點,也不需要自己去寫光源的樣式,而是透過配置傳入 hooks 中
所以 hooks 內部要自己透過操作 DOM 的方式,去添加、刪除光源,可以使用
createElement、appendChild、removeChild
去做這些事~
完整源碼
<!-- Index.vue -->
<template>
<div class="container">
<!-- 方塊盒子 -->
<div class="item"ref="cardRef1"></div>
<!-- 方塊盒子 -->
<div class="item"ref="cardRef2"></div>
<!-- 方塊盒子 -->
<div class="item"ref="cardRef3"></div>
</div>
</template>
<scriptsetuplang="ts">
import { useLightCard } from'./use-light-card';
const { cardRef: cardRef1 } = useLightCard();
const { cardRef: cardRef2 } = useLightCard({
light: {
color: '#ffffff',
width: 100,
},
});
const { cardRef: cardRef3 } = useLightCard({
light: {
color: 'yellow',
},
});</script>
< stylescopedlang="less">
.container {
background: black;
width: 100%;
height: 100%;
padding: 200px;
display: flex;
justify-content: space-between;
.item {
position: relative;
width: 125px;
height: 125px;
background: #1c1c1f;
border: 1px solid rgba(255, 255, 255, 0.1);
}
}
</ style>
// use-light-card.ts
import { onMounted, onUnmounted, ref } from'vue';
interface IOptions {
light?: {
width?: number; // 寬
height?: number; // 高
color?: string; // 顏色
blur?: number; // filter: blur()
};
}
exportconst useLightCard = (option: IOptions = {}) => {
// 獲取卡片的dom節點
const cardRef = ref<HTMLDivElement | null>(null);
let cardOverflow = '';
// 光的dom節點
const lightRef = ref<HTMLDivElement>(document.createElement('div'));
// 設定光源的樣式
const setLight style = () => {
const { width = 60, height = 60, color = '#ff4132', blur = 40 } = option.light ?? {};
const lightDom = lightRef.value;
lightDom. style.position = 'absolute';
lightDom. style.width = `${width}px`;
lightDom. style.height = `${height}px`;
lightDom. style.background = color;
lightDom. style.filter = `blur(${blur}px)`;
};
// 設定卡片的 overflow 為 hidden
const setCardOverflowHidden = () => {
const cardDom = cardRef.value;
if (cardDom) {
cardOverflow = cardDom. style.overflow;
cardDom. style.overflow = 'hidden';
}
};
// 還原卡片的 overflow
const restoreCardOverflow = () => {
const cardDom = cardRef.value;
if (cardDom) {
cardDom. style.overflow = cardOverflow;
}
};
// 往卡片添加光源
const addLight = () => {
const cardDom = cardRef.value;
if (cardDom) {
cardDom.appendChild(lightRef.value);
}
};
// 刪除光源
const removeLight = () => {
const cardDom = cardRef.value;
if (cardDom) {
cardDom.removeChild(lightRef.value);
}
};
// 監聽卡片的滑鼠移入
const onMouseEnter = () => {
// 添加光源
addLight();
setCardOverflowHidden();
};
// use-light-card.ts
// 監聽卡片的滑鼠移動
const onMouseMove = (e: MouseEvent) => {
// 獲取滑鼠的座標
const { clientX, clientY } = e;
// 讓光跟隨滑鼠
const cardDom = cardRef.value;
const lightDom = lightRef.value;
if (cardDom) {
// 獲取卡片相對於視窗的x和y座標
const { x, y } = cardDom.getBoundingClientRect();
// 獲取光的寬高
const { width, height } = lightDom.getBoundingClientRect();
lightDom. style.left = `${clientX - x - width / 2}px`;
lightDom. style.top = `${clientY - y - height /2}px`;
// 設定動畫效果
const maxXRotation = 10; // X 軸旋轉角度
const maxYRotation = 10; // Y 軸旋轉角度
const rangeX = 200 / 2; // X 軸旋轉的範圍
const rangeY = 200 / 2; // Y 軸旋轉的範圍
const rotateX = ((clientX - x - rangeY) / rangeY) * maxXRotation; // 根據滑鼠在 Y 軸上的位置計算繞 X 軸的旋轉角度
const rotateY = -1 * ((clientY - y - rangeX) / rangeX) * maxYRotation; // 根據滑鼠在 X 軸上的位置計算繞 Y 軸的旋轉角度
cardDom. style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`; //設定 3D 透視
}
};
// 監聽卡片滑鼠移出
const onMouseLeave = () => {
// 滑鼠離開移出光源
removeLight();
restoreCardOverflow();
};
onMounted(() => {
// 設定光源樣式
setLight style();
// 繫結事件
cardRef.value?.addEventListener('mouseenter', onMouseEnter);
cardRef.value?.addEventListener('mousemove', onMouseMove);
cardRef.value?.addEventListener('mouseleave', onMouseLeave);
});
onUnmounted(() => {
// 解綁事件
cardRef.value?.removeEventListener('mouseenter', onMouseEnter);
cardRef.value?.removeEventListener('mousemove', onMouseMove);
cardRef.value?.removeEventListener('mouseleave', onMouseLeave);
});
return {
cardRef,
};
};
結語
我是林三心
一個待過 小型toG型外包公司、大型外包公司、小公司、潛力型創業公司、大公司 的作死型前端選手;
一個偏前端的全幹工程師;
一個不正經的金塊作者;
逗比的B站up主;
不帥的小紅書博主;
喜歡打鐵的籃球菜鳥;
喜歡歷史的乏味少年;
喜歡rap的五音不全弱雞如果你想一起學習前端,一起摸魚,一起研究簡歷最佳化,一起研究面試進步,一起交流歷史音樂籃球rap,可以來俺的摸魚學習群哈哈,點這個,有7000多名前端小夥伴在等著一起學習哦 -->
廣州的兄弟可以約飯哦,或者約球~我負責打鐵,你負責進球,謝謝~