當前位置: 妍妍網 > 碼農

Vue3 實作最近很火的酷炫功能:卡片懸浮發光

2024-05-22碼農

模擬面試、簡歷指導、入職指導、計畫指導、答疑解惑 可私信找我~已幫助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',
    width100,
    },
    });
    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多名前端小夥伴在等著一起學習哦 -->

  • 廣州的兄弟可以約飯哦,或者約球~我負責打鐵,你負責進球,謝謝~