成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Vue3 實現(xiàn)最近很火的酷炫功能:卡片懸浮發(fā)光

開發(fā) 前端
剛剛說到實現(xiàn)思路時我們說到了mouseenter、mousemove、mouseleave,其實mouseenter、mouseleave 這二者的邏輯比較簡單,重點是 mouseover 這個監(jiān)聽函數(shù),而在 mouseover 這個函數(shù)中,最重要的邏輯就是:光怎么跟隨鼠標移動呢?

有趣的動畫效果

前幾天在網(wǎng)上看到了一個很有趣的動畫效果,如下,光會跟隨鼠標在卡片上進行移動,并且卡片會有視差的效果。

那么在 Vue3 中應該如何去實現(xiàn)這個效果呢?

圖片圖片

基本實現(xiàn)思路

其實實現(xiàn)思路很簡單,無非就是分幾步:

  • 首先,卡片是相對定位,光是絕對定位
  • 監(jiān)聽卡片的鼠標移入事件mouseenter,當鼠標進入時顯示光
  • 監(jiān)聽卡片的鼠標移動事件mouseover,鼠標移動時修改光的left、top,讓光跟隨鼠標移動
  • 監(jiān)聽卡片的鼠標移出事件mouseleave,鼠標移出時,隱藏光

我們先在 Index.vue 中準備一個卡片頁面,光的CSS效果可以使用filter: blur() 來實現(xiàn)。

圖片圖片

可以看到現(xiàn)在的效果是這樣:

圖片圖片

實現(xiàn)光源跟隨鼠標

在實現(xiàn)之前我們需要注意幾點:

1、鼠標移入時需要設置卡片 overflow: hidden,否則光會溢出,而鼠標移出時記得還原。

2、獲取鼠標坐標時需要用clientX/Y而不是pageX/Y,因為前者會把頁面滾動距離也算進去,比較嚴謹。

剛剛說到實現(xiàn)思路時我們說到了mouseenter、mousemove、mouseleave,其實mouseenter、mouseleave 這二者的邏輯比較簡單,重點是 mouseover 這個監(jiān)聽函數(shù)。

而在 mouseover 這個函數(shù)中,最重要的邏輯就是:光怎么跟隨鼠標移動呢?

或者也可以這么說:怎么計算光相對于卡片盒子的 left 和 top。

對此我專門畫了一張圖,相信大家一看就懂怎么算了。

圖片圖片

  • left = clientX - x - width/2
  • height = clientY - y - height/2

知道了怎么計算,那么邏輯的實現(xiàn)也很明了了~封裝一個use-light-card.ts:

圖片圖片

接著在頁面中去使用:

圖片圖片

這樣就能實現(xiàn)基本的效果啦~

圖片圖片

卡片視差效果

卡片的視差效果需要用到樣式中 transform 樣式,主要是配置四個東西:

  • perspective:定義元素在 3D 變換時的透視效果
  • rotateX:X 軸旋轉(zhuǎn)角度
  • rotateY:Y 軸旋轉(zhuǎn)角度
  • scale3d:X/Y/Z 軸上的縮放比例

圖片圖片

現(xiàn)在就有了卡片視差的效果啦~

圖片圖片

給所有卡片添加光源

上面只是給一個卡片增加光源,接下來可以給每一個卡片都增加光源啦!!!

圖片圖片

圖片圖片

讓光源變成可配置

上面的代碼,總感覺這個 hooks 耦合度太高不太通用,所以我們可以讓光源變成可配置化,這樣每個卡片就可以展示不同大小、顏色的光源了~像下面一樣。

圖片圖片

既然是配置化,那我們希望是這么去使用 hooks 的,我們并不需要自己在頁面中去寫光源的dom節(jié)點,也不需要自己去寫光源的樣式,而是通過配置傳入 hooks 中。

圖片

所以 hooks 內(nèi)部要自己通過操作 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>

<script setup lang="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>

<style scoped lang="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()
  };
}

export const useLightCard = (option: IOptions = {}) => {
  // 獲取卡片的dom節(jié)點
  const cardRef = ref<HTMLDivElement | null>(null);
  let cardOverflow = '';
  // 光的dom節(jié)點
  const lightRef = ref<HTMLDivElement>(document.createElement('div'));
  // 設置光源的樣式

  const setLightStyle = () => {
    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);
    }
  };

  // 監(jiān)聽卡片的鼠標移入
  const onMouseEnter = () => {
    // 添加光源
    addLight();
    setCardOverflowHidden();
  };

  // use-light-card.ts

  // 監(jiān)聽卡片的鼠標移動
  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 軸旋轉(zhuǎn)角度
      const maxYRotation = 10; // Y 軸旋轉(zhuǎn)角度

      const rangeX = 200 / 2; // X 軸旋轉(zhuǎn)的范圍
      const rangeY = 200 / 2; // Y 軸旋轉(zhuǎn)的范圍

      const rotateX = ((clientX - x - rangeY) / rangeY) * maxXRotation; // 根據(jù)鼠標在 Y 軸上的位置計算繞 X 軸的旋轉(zhuǎn)角度
      const rotateY = -1 * ((clientY - y - rangeX) / rangeX) * maxYRotation; // 根據(jù)鼠標在 X 軸上的位置計算繞 Y 軸的旋轉(zhuǎn)角度

      cardDom.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`; //設置 3D 透視
    }
  };
  // 監(jiān)聽卡片鼠標移出
  const onMouseLeave = () => {
    // 鼠標離開移出光源
    removeLight();
    restoreCardOverflow();
  };

  onMounted(() => {
    // 設置光源樣式
    setLightStyle();
    // 綁定事件
    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,
  };
};

責任編輯:武曉燕 來源: 前端之神
相關(guān)推薦

2024-06-04 14:17:26

2021-12-02 05:50:35

Vue3 插件Vue應用

2024-07-10 10:38:58

Vue組件函數(shù)

2022-03-14 09:57:30

Python代碼

2015-12-15 13:43:24

volte

2024-04-18 08:53:15

Vue3數(shù)字動畫

2021-07-22 09:40:10

GitHub代碼開發(fā)者

2025-04-29 00:00:00

超節(jié)點SuperPod大模型

2021-06-09 08:30:52

CSS33D旋轉(zhuǎn)視圖3D動畫

2024-03-25 07:04:12

2024-01-23 09:15:33

Vue3組件拖拽組件內(nèi)容編輯

2025-04-29 08:56:36

2023-09-07 23:06:07

2021-01-05 08:10:00

Css前端3D旋轉(zhuǎn)透視

2022-03-10 11:04:04

Vue3Canvas前端

2025-05-13 08:20:00

Vue3前端動效組件庫

2021-12-01 08:11:44

Vue3 插件Vue應用

2024-02-27 08:27:18

元素拖拽Vue3拼圖驗證

2017-07-11 15:00:04

前端CSS3D視角

2021-06-18 05:59:37

Css前端CSS 特效
點贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 亚洲天堂久久新 | 希岛爱理在线 | 热久久免费视频 | 国产日本精品视频 | 日韩在线免费视频 | 午夜欧美a级理论片915影院 | а_天堂中文最新版地址 | 中文在线a在线 | 国产清纯白嫩初高生在线播放视频 | 91精品国产乱码久久蜜臀 | 一区二区三区免费 | 天堂一区二区三区 | 久久成人av电影 | 日韩成人影院 | 成人在线免费网站 | 亚洲日韩中文字幕一区 | 精品亚洲一区二区三区 | 日韩中文一区 | 国产性生活一级片 | 伊人99| 亚洲综合一区二区三区 | 91网视频 | 久久大香| 国产亚洲精品久久久久久豆腐 | 国产午夜精品视频 | 亚洲欧美一区二区三区国产精品 | 日韩一级黄色毛片 | 日本不卡免费新一二三区 | 69亚洲精品 | 欧美日韩一区二区三区四区 | 欧美视频偷拍 | 日本不卡一区二区三区 | 欧美日韩91 | 先锋资源站| 国产精品成人久久久久a级 久久蜜桃av一区二区天堂 | 欧美成人免费在线视频 | 青草视频在线 | 欧美日韩国产在线观看 | 成人精品在线观看 | 在线资源视频 | 三级在线免费 |