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

使用 React Hooks 實(shí)現(xiàn)鼠標(biāo)懸浮卡片發(fā)光的動(dòng)畫效果

開發(fā)
在網(wǎng)上看到了一個(gè)很有趣的動(dòng)畫效果:光會(huì)跟隨鼠標(biāo)在卡片上進(jìn)行移動(dòng),并且卡片會(huì)有視差的效果。那么在 React 中應(yīng)該如何去實(shí)現(xiàn)這個(gè)效果呢?

有趣的動(dòng)畫效果

前幾天在網(wǎng)上看到了一個(gè)很有趣的動(dòng)畫效果,如下,光會(huì)跟隨鼠標(biāo)在卡片上進(jìn)行移動(dòng),并且卡片會(huì)有視差的效果,那么在 React 中應(yīng)該如何去實(shí)現(xiàn)這個(gè)效果呢?

基本實(shí)現(xiàn)思路

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

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

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

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

實(shí)現(xiàn)光源跟隨鼠標(biāo)

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

  • 鼠標(biāo)移入時(shí)需要設(shè)置卡片 overflow: hidden,否則光會(huì)溢出,而鼠標(biāo)移出時(shí)記得還原
  • 獲取鼠標(biāo)坐標(biāo)時(shí)需要用clientX/Y而不是pageX/Y,因?yàn)榍罢邥?huì)把頁面滾動(dòng)距離也算進(jìn)去,比較嚴(yán)謹(jǐn)

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

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

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

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

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

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

接著在頁面中去使用:

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

卡片視差效果

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

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

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

給所有卡片添加光源

上面只是給一個(gè)卡片增加光源,接下來可以給每一個(gè)卡片都增加光源啦?。?!

讓光源變成可配置

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

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

所以 hooks 內(nèi)部要自己通過操作 DOM 的方式,去添加、刪除光源,可以使用createElement、appendChild、removeChild 去做這些事~

完整源碼

// use-light-card.ts

import { useEffect, useRef } from 'react';

interface IOptions {
  light?: {
    width?: number; // 寬
    height?: number; // 高
    color?: string; // 顏色
    blur?: number; // filter: blur()
  };
}

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

  const setLightStyle = () => {
    const { width = 60, height = 60, color = '#ff4132', blur = 40 } = option.light ?? {};
    const lightDom = lightRef.current;
    lightDom.style.position = 'absolute';
    lightDom.style.width = `${width}px`;
    lightDom.style.height = `${height}px`;
    lightDom.style.background = color;
    lightDom.style.filter = `blur(${blur}px)`;
  };

  // 設(shè)置卡片的 overflow 為 hidden
  const setCardOverflowHidden = () => {
    const cardDom = cardRef.current;
    if (cardDom) {
      cardOverflow = cardDom.style.overflow;
      cardDom.style.overflow = 'hidden';
    }
  };
  // 還原卡片的 overflow
  const restoreCardOverflow = () => {
    const cardDom = cardRef.current;
    if (cardDom) {
      cardDom.style.overflow = cardOverflow;
    }
  };

  // 往卡片添加光源
  const addLight = () => {
    const cardDom = cardRef.current;
    if (cardDom) {
      cardDom.appendChild(lightRef.current);
    }
  };
  // 刪除光源
  const removeLight = () => {
    const cardDom = cardRef.current;
    if (cardDom) {
      cardDom.removeChild(lightRef.current);
    }
  };

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

  // use-light-card.ts

  // 監(jiān)聽卡片的鼠標(biāo)移動(dòng)
  const onMouseMove = (e: MouseEvent) => {
    // 獲取鼠標(biāo)的坐標(biāo)
    const { clientX, clientY } = e;
    // 讓光跟隨鼠標(biāo)
    const cardDom = cardRef.current;
    const lightDom = lightRef.current;
    if (cardDom) {
      // 獲取卡片相對于窗口的x和y坐標(biāo)
      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`;

      //   設(shè)置動(dòng)畫效果
      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ù)鼠標(biāo)在 Y 軸上的位置計(jì)算繞 X 軸的旋轉(zhuǎn)角度
      const rotateY = -1 * ((clientY - y - rangeX) / rangeX) * maxYRotation; // 根據(jù)鼠標(biāo)在 X 軸上的位置計(jì)算繞 Y 軸的旋轉(zhuǎn)角度

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

  useEffect(() => {
    // 設(shè)置光源樣式
    setLightStyle();
    // 綁定事件
    cardRef.current?.addEventListener('mouseenter', onMouseEnter);
    cardRef.current?.addEventListener('mousemove', onMouseMove);
    cardRef.current?.addEventListener('mouseleave', onMouseLeave);
    return () => {
        // 解綁事件
        cardRef.current?.removeEventListener('mouseenter', onMouseEnter);
        cardRef.current?.removeEventListener('mousemove', onMouseMove);
        cardRef.current?.removeEventListener('mouseleave', onMouseLeave);
    }
  })

  return {
    cardRef,
  };
};

// Index.tsx
import './Index.less'
import { useLightCard } from './use-light-card'

const Index = () => {
    const { cardRef: cardRef1 } = useLightCard()
    const { cardRef: cardRef2 } = useLightCard({
        light: {
            color: '#ffffff',
            width: 100
        }
    })
    const { cardRef: cardRef3 } = useLightCard({
        light: {
            color: 'yellow'
        }
    })

    return <div className='light-card-container'>
        {/* 方塊盒子 */}
        <div className='item' ref={cardRef1}></div>
        {/* 方塊盒子 */}
        <div className='item' ref={cardRef2}></div>
        {/* 方塊盒子 */}
        <div className='item' ref={cardRef3}></div>
    </div>
}

export default Index
// Index.less

.light-card-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);

      // 不需要了
      // .light {
      //   width: 60px;
      //   height: 60px;
      //   background: #ff4132;
      //   filter: blur(40px);
      //   position: absolute;
      // }
    }
}

結(jié)語

責(zé)任編輯:趙寧寧 來源: 前端之神
相關(guān)推薦

2017-02-06 13:00:49

Android翻轉(zhuǎn)卡片動(dòng)畫效果

2024-05-22 08:47:41

2020-10-28 09:12:48

React架構(gòu)Hooks

2022-04-16 20:10:00

React Hookfiber框架

2022-08-21 09:41:42

ReactVue3前端

2022-07-18 09:01:58

React函數(shù)組件Hooks

2022-02-10 19:15:18

React監(jiān)聽系統(tǒng)模式

2019-08-20 15:16:26

Reacthooks前端

2023-11-06 08:00:00

ReactJavaScript開發(fā)

2011-07-08 10:15:15

IPhone 動(dòng)畫

2021-03-09 09:52:55

技術(shù)React Hooks'數(shù)據(jù)

2009-09-22 12:59:58

ibmdwDojo

2021-03-18 08:00:55

組件Hooks React

2022-03-31 17:54:29

ReactHooks前端

2020-09-19 17:46:20

React Hooks開發(fā)函數(shù)

2009-09-03 16:50:35

C#鼠標(biāo)形狀

2021-02-24 07:40:38

React Hooks閉包

2019-03-13 10:10:26

React組件前端

2012-01-10 14:59:42

jQuery

2010-09-09 12:49:58

鼠標(biāo)懸停tip效果CSS
點(diǎn)贊
收藏

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

主站蜘蛛池模板: 久久在线| 青青草一区二区 | 性国产丰满麻豆videosex | 一级毛片免费完整视频 | 欧美福利三区 | 国产亚洲一区二区三区 | 国产精品爱久久久久久久 | 亚洲 中文 欧美 日韩 在线观看 | 国产日产精品一区二区三区四区 | 人人草人人干 | 亚洲精品久久久一区二区三区 | 成人午夜视频在线观看 | 一区二区视屏 | 丁香五月网久久综合 | 欧美视频二区 | 日韩电影一区 | av网址在线播放 | 7777久久 | 国产av毛片 | 中文字幕亚洲精品 | 国产电影一区二区三区爱妃记 | 国产精品一区二区三区四区 | 精品福利在线视频 | 在线一区 | 国产毛片在线看 | 涩涩片影院 | 欧美精品网 | 久久日本 | 国产精品久久久久久久久久久久冷 | 桃花av在线 | 亚洲三区在线观看 | 在线日韩福利 | 久久久久久久国产 | 亚洲精品电影在线观看 | 97日日碰人人模人人澡分享吧 | 久久久久久www | 日本在线免费视频 | 国产成人高清成人av片在线看 | 精品成人免费一区二区在线播放 | 一区二区精品 | 一区二区国产在线 |