最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

banner 中 logo 聚合分散動畫

2023-03-17 08:05 作者:程序員-王堅  | 我要投稿

1. 效果展示


在線查看

2. 開始前說明

效果實現(xiàn)參考源碼:Logo 聚集與散開

原效果代碼基于 react jsx 類組件實現(xiàn)。依賴舊,代碼冗余。

我將基于此進行重構(gòu),重構(gòu)目標:

  • 基于最新依賴包,用 ts + hook 實現(xiàn)效果

  • 簡化 dom 結(jié)構(gòu)及樣式

  • 支持響應(yīng)式

重構(gòu)應(yīng)該在還原的基礎(chǔ)上,用更好的方式實現(xiàn)相同的效果。如果能讓功能更完善,那就更好了。

在重構(gòu)的過程中,注意理解:

  • 嚴格模式

  • 獲取不到最新數(shù)據(jù),setState 異步更新,useRef 同步最新數(shù)據(jù)

  • 類組件生命周期,如何轉(zhuǎn)換為 hook

  • canvas 上繪圖獲取圖像數(shù)據(jù),并對數(shù)據(jù)進行處理

3. 重構(gòu)

說明:后面都是代碼,對代碼感興趣的可以與源碼比較一下;對效果感興趣的,希望對你有幫助!

腳手架:vite-react+ts

3.1 刪除多余文件及代碼,只留最簡單的結(jié)構(gòu)

  • 修改入口文件?main.tsx?為:

import ReactDOM from "react-dom/client";import App from "./App";ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( ?<App />);

注意:這兒刪除了嚴格模式

  • 刪除?index.css

  • 修改?App.tsx?為:

import "./App.css";function App() { ?return ( ? ?<div className="App"> ? ? ? ? ?</div> ?); }export default App;

  • 修改?App.css?為:

* { ?margin: 0; ?padding: 0; ?box-sizing: border-box; }

3.3 安裝依賴

yarn add rc-tween-one lodash-es -S yarn add @types/lodash-es -D

rc-tween-one:Ant Motion?的一個動效組件

3.4 重構(gòu)代碼

APP.tsx

import TweenOne from "rc-tween-one";import LogoAnimate from "./logoAnimate";import "./App.css";function App() { ?return ( ? ?<div className="App"> ? ? ?<div className="banner"> ? ? ? ?<div className="content"> ? ? ? ? ?<TweenOne ? ? ? ? ? ?animation={{ opacity: 0, y: -30, type: "from", delay: 500 }} ? ? ? ? ? ?className="title" ? ? ? ? ?> ? ? ? ? ? ?logo 聚合分散 ? ? ? ? ?</TweenOne> ? ? ? ?</div> ? ? ? ?<LogoAnimate /> ? ? ?</div> ? ?</div> ?); }export default App;

App.css

* { ?margin: 0; ?padding: 0; ?box-sizing: border-box; }.banner { ?width: 100%; ?height: 100vh; ?overflow: hidden; ?background: linear-gradient(135deg, #35aef8 0%, #7681ff 76%, #7681ff 76%); ?position: relative; ?display: flex; ?align-items: center; ?justify-content: space-evenly; }.banner .content { ?height: 35%; ?color: #fff; }.banner .content .title { ?font-size: 40px; ?background: linear-gradient(yellow, white); ?-webkit-background-clip: text; ?color: transparent; }.banner .logo-box { ?width: 300px; ?height: 330px; }.banner .logo-box * { ?pointer-events: none; }.banner .logo-box img { ?margin-left: 70px; ?transform: scale(1.5); ?margin-top: 60px; ?opacity: 0.4; }.banner .logo-box .point-wrap { ?position: absolute; }.banner .logo-box .point-wrap .point { ?border-radius: 100%; }@media screen and (max-width: 767px) { ?.banner { ? ?flex-direction: column; ?} ?.banner .content { ? ?order: 1; ?} } * { ?margin: 0; ?padding: 0; ?box-sizing: border-box; }.banner { ?width: 100%; ?height: 100vh; ?overflow: hidden; ?background: linear-gradient(135deg, #35aef8 0%, #7681ff 76%, #7681ff 76%); ?position: relative; ?display: flex; ?align-items: center; ?justify-content: space-evenly; }.banner .content { ?height: 35%; ?color: #fff; }.banner .content .title { ?font-size: 30px; }.banner .logo-box { ?width: 300px; ?height: 330px; }.banner .logo-box * { ?pointer-events: none; }.banner .logo-box img { ?margin-left: 70px; ?transform: scale(1.5); ?margin-top: 60px; ?opacity: 0.4; }.banner .logo-box .point-wrap { ?position: absolute; }.banner .logo-box .point-wrap .point { ?border-radius: 100%; }@media screen and (max-width: 767px) { ?.banner { ? ?flex-direction: column; ?} ?.banner .content { ? ?order: 1; ?} }

重點重構(gòu)文件 logoAnimate.tsx

import React, { useRef, useState, useEffect } from "react";import TweenOne, { Ticker } from "rc-tween-one";import type { IAnimObject } from "rc-tween-one";import { cloneDeep, delay } from "lodash-es";type Point = { ?wrapStyle: { ? ?left: number; ? ?top: number; ?}; ?style: { ? ?width: number; ? ?height: number; ? ?opacity: number; ? ?backgroundColor: string; ?}; ?animation: IAnimObject; };const logoAnimate = () => { ?const data = { ? ?image: ? ? ?"https://imagev2.xmcdn.com/storages/f390-audiofreehighqps/4C/D1/GKwRIDoHwne3AABEqQH4FjLV.png", ? ?w: 200, // 圖片實際的寬度 ? ?h: 200, // 圖片實際的高度 ? ?scale: 1.5, // 顯示時需要的縮放比例 ? ?pointSizeMin: 10, // 顯示時圓點最小的大小 ?}; ?const intervalRef = useRef<string | null>(null); ?const intervalTime = 5000; ?const initAnimateTime = 800; ?const logoBoxRef = useRef<HTMLDivElement>(null); ?// 聚合:true,保證永遠拿到的是最新的數(shù)據(jù),useState是異步的,在interval中拿不到 ?const gatherRef = useRef(true); ?// 數(shù)據(jù)變更,促使dom變更 ?const [points, setPoints] = useState<Point[]>([]); ?// 同步 points 數(shù)據(jù),保證永遠拿到的是最新的數(shù)據(jù),useState是異步的,在interval中拿不到 ?const pointsRef = useRef(points); ?useEffect(() => { ? ?pointsRef.current = points; ?}, [points]); ?const setDataToDom = (imgData: Uint8ClampedArray, w: number, h: number) => { ? ?const pointArr: { x: number; y: number; r: number }[] = []; ? ?const num = Math.round(w / 10); ? ?for (let i = 0; i < w; i += num) { ? ? ?for (let j = 0; j < h; j += num) { ? ? ? ?const index = (i + j * w) * 4 + 3; ? ? ? ?if (imgData[index] > 150) { ? ? ? ? ?pointArr.push({ ? ? ? ? ? ?x: i, ? ? ? ? ? ?y: j, ? ? ? ? ? ?r: Math.random() * data.pointSizeMin + 12 ? ? ? ? ?}); ? ? ? ?} ? ? ?} ? ?} ? ?const newPoints = pointArr.map((item, i) => { ? ? ?const opacity = Math.random() * 0.4 + 0.1; ? ? ?const point: Point = { ? ? ? ?wrapStyle: { left: item.x * data.scale, top: item.y * data.scale }, ? ? ? ?style: { ? ? ? ? ?width: item.r * data.scale, ? ? ? ? ?height: item.r * data.scale, ? ? ? ? ?opacity: opacity, ? ? ? ? ?backgroundColor: `rgb(${Math.round(Math.random() * 95 + 160)}, 255, 255)`, ? ? ? ?}, ? ? ? ?animation: { ? ? ? ? ?y: (Math.random() * 2 - 1) * 10 || 5, ? ? ? ? ?x: (Math.random() * 2 - 1) * 5 || 2.5, ? ? ? ? ?delay: Math.random() * 1000, ? ? ? ? ?repeat: -1, ? ? ? ? ?duration: 3000, ? ? ? ? ?ease: "easeInOutQuad", ? ? ? ?}, ? ? ?}; ? ? ?return point; ? ?}); ? ?delay(() => { ? ? ?setPoints(newPoints); ? ?}, initAnimateTime + 150); ? ?intervalRef.current = Ticker.interval(updateTweenData, intervalTime); ?}; ?const createPointData = () => { ? ?const { w, h } = data; ? ?const canvas = document.createElement("canvas"); ? ?const ctx = canvas.getContext("2d"); ? ?if (!ctx) return; ? ?ctx.clearRect(0, 0, w, h); ? ?canvas.width = w; ? ?canvas.height = h; ? ?const img = new Image(); ? ?img.crossOrigin = "anonymous"; ? ?img.src = data.image; ? ?img.onload = () => { ? ? ?ctx.drawImage(img, 0, 0); ? ? ?const data = ctx.getImageData(0, 0, w, h).data; ? ? ?setDataToDom(data, w, h); ? ?}; ?}; ?useEffect(() => { ? ?createPointData(); ? ?return () => { ? ? ?removeInterval(); ? ?}; ?}, []); ?// 分散數(shù)據(jù) ?const disperseData = () => { ? ?if (!logoBoxRef.current || !logoBoxRef.current.parentElement) return; ? ?const rect = logoBoxRef.current.parentElement.getBoundingClientRect(); ? ?const boxRect = logoBoxRef.current.getBoundingClientRect(); ? ?const boxTop = boxRect.top - rect.top; ? ?const boxLeft = boxRect.left - rect.left; ? ?const newPoints = cloneDeep(pointsRef.current).map((item) => ({ ? ? ?...item, ? ? ?animation: { ? ? ? ?x: Math.random() * rect.width - boxLeft - item.wrapStyle.left, ? ? ? ?y: Math.random() * rect.height - boxTop - item.wrapStyle.top, ? ? ? ?opacity: Math.random() * 0.2 + 0.1, ? ? ? ?scale: Math.random() * 2.4 + 0.1, ? ? ? ?duration: Math.random() * 500 + 500, ? ? ? ?ease: "easeInOutQuint", ? ? ?}, ? ?})); ? ?setPoints(newPoints); ?}; ?// 聚合數(shù)據(jù) ?const gatherData = () => { ? ?const newPoints = cloneDeep(pointsRef.current).map((item) => ({ ? ? ?...item, ? ? ?animation: { ? ? ? ?x: 0, ? ? ? ?y: 0, ? ? ? ?opacity: Math.random() * 0.2 + 0.1, ? ? ? ?scale: 1, ? ? ? ?delay: Math.random() * 500, ? ? ? ?duration: 800, ? ? ? ?ease: "easeInOutQuint", ? ? ?}, ? ?})); ? ?setPoints(newPoints); ?}; ?const updateTweenData = () => { ? ?gatherRef.current ? disperseData() : gatherData(); ? ?gatherRef.current = !gatherRef.current; ?}; ?const removeInterval = () => { ? ?if (intervalRef.current) { ? ? ?Ticker.clear(intervalRef.current); ? ? ?intervalRef.current = null; ? ?} ?}; ?const onMouseEnter = () => { ? ?if (!gatherRef.current) { ? ? ?updateTweenData(); ? ?} ? ?removeInterval(); ?}; ?const onMouseLeave = () => { ? ?if (gatherRef.current) { ? ? ?updateTweenData(); ? ?} ? ?intervalRef.current = Ticker.interval(updateTweenData, intervalTime); ?}; ?return ( ? ?<> ? ? ?{points.length === 0 ? ( ? ? ? ?<TweenOne ? ? ? ? ?className="logo-box" ? ? ? ? ?animation={{ ? ? ? ? ? ?opacity: 0.8, ? ? ? ? ? ?scale: 1.5, ? ? ? ? ? ?rotate: 35, ? ? ? ? ? ?type: "from", ? ? ? ? ? ?duration: initAnimateTime, ? ? ? ? ?}} ? ? ? ?> ? ? ? ? ?<img key="img" src={data.image} alt="" /> ? ? ? ?</TweenOne> ? ? ?) : ( ? ? ? ?<TweenOne ? ? ? ? ?animation={{ opacity: 0, type: "from", duration: 800 }} ? ? ? ? ?className="logo-box" ? ? ? ? ?onMouseEnter={onMouseEnter} ? ? ? ? ?onMouseLeave={onMouseLeave} ? ? ? ? ?ref={logoBoxRef} ? ? ? ?> ? ? ? ? ?{points.map((item, i) => ( ? ? ? ? ? ?<TweenOne className="point-wrap" key={i} style={item.wrapStyle}> ? ? ? ? ? ? ?<TweenOne ? ? ? ? ? ? ? ?className="point" ? ? ? ? ? ? ? ?style={item.style} ? ? ? ? ? ? ? ?animation={item.animation} ? ? ? ? ? ? ?/> ? ? ? ? ? ?</TweenOne> ? ? ? ? ?))} ? ? ? ?</TweenOne> ? ? ?)} ? ?</> ?); };export default logoAnimate;


banner 中 logo 聚合分散動畫的評論 (共 條)

分享到微博請遵守國家法律
淅川县| 瓦房店市| 惠安县| 莲花县| 康平县| 蒙山县| 赣榆县| 昌乐县| 磐石市| 黄平县| 隆安县| 凤台县| 富川| 克东县| 彩票| 巴青县| 衡山县| 固阳县| 乌拉特中旗| 甘谷县| 金门县| 望江县| 库伦旗| 吉安市| 错那县| 盐山县| 亚东县| 河间市| 武义县| 青神县| 新泰市| 抚顺县| 和田市| 龙游县| 墨江| 新营市| 长岭县| 松原市| 扎兰屯市| 贵州省| 邮箱|