From 12341ad9ec8ba961779732edf0b810007aa2d6e8 Mon Sep 17 00:00:00 2001 From: EnderRomantice Date: Fri, 5 Dec 2025 15:38:32 +0800 Subject: [PATCH 1/4] perf: Optimize animation loop by reducing unnecessary DOM writes for content/TextPressure.jsx --- .../TextPressure/TextPressure.jsx | 98 +++++++++++-------- 1 file changed, 59 insertions(+), 39 deletions(-) diff --git a/src/content/TextAnimations/TextPressure/TextPressure.jsx b/src/content/TextAnimations/TextPressure/TextPressure.jsx index e0858130..e337eb8c 100644 --- a/src/content/TextAnimations/TextPressure/TextPressure.jsx +++ b/src/content/TextAnimations/TextPressure/TextPressure.jsx @@ -1,11 +1,31 @@ // Component ported from https://codepen.io/JuanFuentes/full/rgXKGQ -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState, useMemo, useCallback } from 'react'; + +const dist = (a, b) => { + const dx = b.x - a.x; + const dy = b.y - a.y; + return Math.sqrt(dx * dx + dy * dy); +}; + +const getAttr = (distance, maxDist, minVal, maxVal) => { + const val = maxVal - Math.abs((maxVal * distance) / maxDist); + return Math.max(minVal, val + minVal); +}; + +const debounce = (func, delay) => { + let timeoutId; + return (...args) => { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => { + func.apply(this, args); + }, delay); + }; +}; const TextPressure = ({ text = 'Compressa', fontFamily = 'Compressa VF', - // This font is just an example, you should not use it in commercial projects. fontUrl = 'https://res.cloudinary.com/dr6lvwubh/raw/upload/v1529908256/CompressaPRO-GX.woff2', width = true, @@ -36,12 +56,6 @@ const TextPressure = ({ const chars = text.split(''); - const dist = (a, b) => { - const dx = b.x - a.x; - const dy = b.y - a.y; - return Math.sqrt(dx * dx + dy * dy); - }; - useEffect(() => { const handleMouseMove = e => { cursorRef.current.x = e.clientX; @@ -54,7 +68,7 @@ const TextPressure = ({ }; window.addEventListener('mousemove', handleMouseMove); - window.addEventListener('touchmove', handleTouchMove, { passive: false }); + window.addEventListener('touchmove', handleTouchMove, { passive: true }); if (containerRef.current) { const { left, top, width, height } = containerRef.current.getBoundingClientRect(); @@ -70,7 +84,7 @@ const TextPressure = ({ }; }, []); - const setSize = () => { + const setSize = useCallback(() => { if (!containerRef.current || !titleRef.current) return; const { width: containerW, height: containerH } = containerRef.current.getBoundingClientRect(); @@ -92,14 +106,14 @@ const TextPressure = ({ setLineHeight(yRatio); } }); - }; + }, [chars.length, minFontSize, scale]); useEffect(() => { - setSize(); - window.addEventListener('resize', setSize); - return () => window.removeEventListener('resize', setSize); - // eslint-disable-next-line - }, [scale, text]); + const debouncedSetSize = debounce(setSize, 100); + debouncedSetSize(); + window.addEventListener('resize', debouncedSetSize); + return () => window.removeEventListener('resize', debouncedSetSize); + }, [setSize]); useEffect(() => { let rafId; @@ -122,18 +136,19 @@ const TextPressure = ({ const d = dist(mouseRef.current, charCenter); - const getAttr = (distance, minVal, maxVal) => { - const val = maxVal - Math.abs((maxVal * distance) / maxDist); - return Math.max(minVal, val + minVal); - }; + const wdth = width ? Math.floor(getAttr(d, maxDist, 5, 200)) : 100; + const wght = weight ? Math.floor(getAttr(d, maxDist, 100, 900)) : 400; + const italVal = italic ? getAttr(d, maxDist, 0, 1).toFixed(2) : 0; + const alphaVal = alpha ? getAttr(d, maxDist, 0, 1).toFixed(2) : 1; - const wdth = width ? Math.floor(getAttr(d, 5, 200)) : 100; - const wght = weight ? Math.floor(getAttr(d, 100, 900)) : 400; - const italVal = italic ? getAttr(d, 0, 1).toFixed(2) : 0; - const alphaVal = alpha ? getAttr(d, 0, 1).toFixed(2) : 1; + const newFontVariationSettings = `'wght' ${wght}, 'wdth' ${wdth}, 'ital' ${italVal}`; - span.style.opacity = alphaVal; - span.style.fontVariationSettings = `'wght' ${wght}, 'wdth' ${wdth}, 'ital' ${italVal}`; + if (span.style.fontVariationSettings !== newFontVariationSettings) { + span.style.fontVariationSettings = newFontVariationSettings; + } + if (alpha && span.style.opacity !== alphaVal) { + span.style.opacity = alphaVal; + } }); } @@ -142,20 +157,10 @@ const TextPressure = ({ animate(); return () => cancelAnimationFrame(rafId); - }, [width, weight, italic, alpha, chars.length]); + }, [width, weight, italic, alpha]); - const dynamicClassName = [className, flex ? 'flex' : '', stroke ? 'stroke' : ''].filter(Boolean).join(' '); - - return ( -
+ const styleElement = useMemo(() => { + return ( + ); + }, [fontFamily, fontUrl, flex, stroke, textColor, strokeColor]); + const dynamicClassName = [className, flex ? 'flex' : '', stroke ? 'stroke' : ''].filter(Boolean).join(' '); + + return ( +
+ {styleElement}

Date: Fri, 5 Dec 2025 15:45:38 +0800 Subject: [PATCH 2/4] perf: Optimize animation loop by reducing unnecessary DOM writes for tailwind/TextPressure.jsx --- .../TextPressure/TextPressure.jsx | 77 ++++++++++++------- 1 file changed, 49 insertions(+), 28 deletions(-) diff --git a/src/tailwind/TextAnimations/TextPressure/TextPressure.jsx b/src/tailwind/TextAnimations/TextPressure/TextPressure.jsx index d1c9726e..ef867126 100644 --- a/src/tailwind/TextAnimations/TextPressure/TextPressure.jsx +++ b/src/tailwind/TextAnimations/TextPressure/TextPressure.jsx @@ -1,6 +1,27 @@ // Component ported from https://codepen.io/JuanFuentes/full/rgXKGQ -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState, useMemo, useCallback } from 'react'; + +const dist = (a, b) => { + const dx = b.x - a.x; + const dy = b.y - a.y; + return Math.sqrt(dx * dx + dy * dy); +}; + +const getAttr = (distance, maxDist, minVal, maxVal) => { + const val = maxVal - Math.abs((maxVal * distance) / maxDist); + return Math.max(minVal, val + minVal); +}; + +const debounce = (func, delay) => { + let timeoutId; + return (...args) => { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => { + func.apply(this, args); + }, delay); + }; +}; const TextPressure = ({ text = 'Compressa', @@ -37,12 +58,6 @@ const TextPressure = ({ const chars = text.split(''); - const dist = (a, b) => { - const dx = b.x - a.x; - const dy = b.y - a.y; - return Math.sqrt(dx * dx + dy * dy); - }; - useEffect(() => { const handleMouseMove = e => { cursorRef.current.x = e.clientX; @@ -55,7 +70,7 @@ const TextPressure = ({ }; window.addEventListener('mousemove', handleMouseMove); - window.addEventListener('touchmove', handleTouchMove, { passive: false }); + window.addEventListener('touchmove', handleTouchMove, { passive: true }); if (containerRef.current) { const { left, top, width, height } = containerRef.current.getBoundingClientRect(); @@ -71,7 +86,7 @@ const TextPressure = ({ }; }, []); - const setSize = () => { + const setSize = useCallback(() => { if (!containerRef.current || !titleRef.current) return; const { width: containerW, height: containerH } = containerRef.current.getBoundingClientRect(); @@ -93,14 +108,14 @@ const TextPressure = ({ setLineHeight(yRatio); } }); - }; + }, [chars.length, minFontSize, scale]); useEffect(() => { - setSize(); - window.addEventListener('resize', setSize); - return () => window.removeEventListener('resize', setSize); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [scale, text]); + const debouncedSetSize = debounce(setSize, 100); + debouncedSetSize(); + window.addEventListener('resize', debouncedSetSize); + return () => window.removeEventListener('resize', debouncedSetSize); + }, [setSize]); useEffect(() => { let rafId; @@ -123,18 +138,19 @@ const TextPressure = ({ const d = dist(mouseRef.current, charCenter); - const getAttr = (distance, minVal, maxVal) => { - const val = maxVal - Math.abs((maxVal * distance) / maxDist); - return Math.max(minVal, val + minVal); - }; + const wdth = width ? Math.floor(getAttr(d, maxDist, 5, 200)) : 100; + const wght = weight ? Math.floor(getAttr(d, maxDist, 100, 900)) : 400; + const italVal = italic ? getAttr(d, maxDist, 0, 1).toFixed(2) : 0; + const alphaVal = alpha ? getAttr(d, maxDist, 0, 1).toFixed(2) : 1; - const wdth = width ? Math.floor(getAttr(d, 5, 200)) : 100; - const wght = weight ? Math.floor(getAttr(d, 100, 900)) : 400; - const italVal = italic ? getAttr(d, 0, 1).toFixed(2) : 0; - const alphaVal = alpha ? getAttr(d, 0, 1).toFixed(2) : 1; + const newFontVariationSettings = `'wght' ${wght}, 'wdth' ${wdth}, 'ital' ${italVal}`; - span.style.opacity = alphaVal; - span.style.fontVariationSettings = `'wght' ${wght}, 'wdth' ${wdth}, 'ital' ${italVal}`; + if (span.style.fontVariationSettings !== newFontVariationSettings) { + span.style.fontVariationSettings = newFontVariationSettings; + } + if (alpha && span.style.opacity !== alphaVal) { + span.style.opacity = alphaVal; + } }); } @@ -143,10 +159,10 @@ const TextPressure = ({ animate(); return () => cancelAnimationFrame(rafId); - }, [width, weight, italic, alpha, chars.length]); + }, [width, weight, italic, alpha]); - return ( -
+ const styleElement = useMemo(() => { + return ( + ); + }, [fontFamily, fontUrl, stroke, textColor, strokeColor, strokeWidth]); + return ( +
+ {styleElement}

Date: Fri, 5 Dec 2025 16:01:45 +0800 Subject: [PATCH 3/4] perf: Optimize animation loop by reducing unnecessary DOM writes and type safe for ts-default/TextPressure.tsx --- .../TextPressure/TextPressure.tsx | 96 ++++++++++++------- 1 file changed, 59 insertions(+), 37 deletions(-) diff --git a/src/ts-default/TextAnimations/TextPressure/TextPressure.tsx b/src/ts-default/TextAnimations/TextPressure/TextPressure.tsx index 8fccfc87..d11ebc12 100644 --- a/src/ts-default/TextAnimations/TextPressure/TextPressure.tsx +++ b/src/ts-default/TextAnimations/TextPressure/TextPressure.tsx @@ -1,6 +1,6 @@ // Component ported from https://codepen.io/JuanFuentes/full/rgXKGQ -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState, useMemo, useCallback } from 'react'; interface TextPressureProps { text?: string; @@ -19,6 +19,27 @@ interface TextPressureProps { minFontSize?: number; } +const dist = (a: { x: number; y: number }, b: { x: number; y: number }) => { + const dx = b.x - a.x; + const dy = b.y - a.y; + return Math.sqrt(dx * dx + dy * dy); +}; + +const getAttr = (distance: number, maxDist: number, minVal: number, maxVal: number) => { + const val = maxVal - Math.abs((maxVal * distance) / maxDist); + return Math.max(minVal, val + minVal); +}; + +const debounce = (func: (...args: any[]) => void, delay: number) => { + let timeoutId: NodeJS.Timeout; + return (...args: any[]) => { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => { + func.apply(this, args); + }, delay); + }; +}; + const TextPressure: React.FC = ({ text = 'Compressa', fontFamily = 'Compressa VF', @@ -48,12 +69,6 @@ const TextPressure: React.FC = ({ const chars = text.split(''); - const dist = (a: { x: number; y: number }, b: { x: number; y: number }) => { - const dx = b.x - a.x; - const dy = b.y - a.y; - return Math.sqrt(dx * dx + dy * dy); - }; - useEffect(() => { const handleMouseMove = (e: MouseEvent) => { cursorRef.current.x = e.clientX; @@ -66,7 +81,7 @@ const TextPressure: React.FC = ({ }; window.addEventListener('mousemove', handleMouseMove); - window.addEventListener('touchmove', handleTouchMove, { passive: false }); + window.addEventListener('touchmove', handleTouchMove, { passive: true }); if (containerRef.current) { const { left, top, width, height } = containerRef.current.getBoundingClientRect(); @@ -82,7 +97,7 @@ const TextPressure: React.FC = ({ }; }, []); - const setSize = () => { + const setSize = useCallback(() => { if (!containerRef.current || !titleRef.current) return; const { width: containerW, height: containerH } = containerRef.current.getBoundingClientRect(); @@ -104,13 +119,14 @@ const TextPressure: React.FC = ({ setLineHeight(yRatio); } }); - }; + }, [chars.length, minFontSize, scale]); useEffect(() => { - setSize(); - window.addEventListener('resize', setSize); - return () => window.removeEventListener('resize', setSize); - }, [scale, text]); + const debouncedSetSize = debounce(setSize, 100); + debouncedSetSize(); + window.addEventListener('resize', debouncedSetSize); + return () => window.removeEventListener('resize', debouncedSetSize); + }, [setSize]); useEffect(() => { let rafId: number; @@ -133,18 +149,19 @@ const TextPressure: React.FC = ({ const d = dist(mouseRef.current, charCenter); - const getAttr = (distance: number, minVal: number, maxVal: number) => { - const val = maxVal - Math.abs((maxVal * distance) / maxDist); - return Math.max(minVal, val + minVal); - }; + const wdth = width ? Math.floor(getAttr(d, maxDist, 5, 200)) : 100; + const wght = weight ? Math.floor(getAttr(d, maxDist, 100, 900)) : 400; + const italVal = italic ? getAttr(d, maxDist, 0, 1).toFixed(2) : '0'; + const alphaVal = alpha ? getAttr(d, maxDist, 0, 1).toFixed(2) : '1'; - const wdth = width ? Math.floor(getAttr(d, 5, 200)) : 100; - const wght = weight ? Math.floor(getAttr(d, 100, 900)) : 400; - const italVal = italic ? getAttr(d, 0, 1).toFixed(2) : 0; - const alphaVal = alpha ? getAttr(d, 0, 1).toFixed(2) : 1; + const newFontVariationSettings = `'wght' ${wght}, 'wdth' ${wdth}, 'ital' ${italVal}`; - span.style.opacity = alphaVal.toString(); - span.style.fontVariationSettings = `'wght' ${wght}, 'wdth' ${wdth}, 'ital' ${italVal}`; + if (span.style.fontVariationSettings !== newFontVariationSettings) { + span.style.fontVariationSettings = newFontVariationSettings; + } + if (alpha && span.style.opacity !== alphaVal) { + span.style.opacity = alphaVal; + } }); } @@ -153,20 +170,10 @@ const TextPressure: React.FC = ({ animate(); return () => cancelAnimationFrame(rafId); - }, [width, weight, italic, alpha, chars.length]); + }, [width, weight, italic, alpha]); - const dynamicClassName = [className, flex ? 'flex' : '', stroke ? 'stroke' : ''].filter(Boolean).join(' '); - - return ( -
+ const styleElement = useMemo(() => { + return ( + ); + }, [fontFamily, fontUrl, flex, stroke, textColor, strokeColor]); + const dynamicClassName = [className, flex ? 'flex' : '', stroke ? 'stroke' : ''].filter(Boolean).join(' '); + + return ( +
+ {styleElement}

Date: Fri, 5 Dec 2025 16:04:57 +0800 Subject: [PATCH 4/4] perf: Optimize animation loop by reducing unnecessary DOM writes and type safe for ts-tailwind/TextPressure.tsx --- .../TextPressure/TextPressure.tsx | 76 ++++++++++++------- 1 file changed, 49 insertions(+), 27 deletions(-) diff --git a/src/ts-tailwind/TextAnimations/TextPressure/TextPressure.tsx b/src/ts-tailwind/TextAnimations/TextPressure/TextPressure.tsx index 9c12f576..d473c8b3 100644 --- a/src/ts-tailwind/TextAnimations/TextPressure/TextPressure.tsx +++ b/src/ts-tailwind/TextAnimations/TextPressure/TextPressure.tsx @@ -1,6 +1,6 @@ // Component ported from https://codepen.io/JuanFuentes/full/rgXKGQ -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState, useMemo, useCallback } from 'react'; interface TextPressureProps { text?: string; @@ -20,6 +20,27 @@ interface TextPressureProps { minFontSize?: number; } +const dist = (a: { x: number; y: number }, b: { x: number; y: number }) => { + const dx = b.x - a.x; + const dy = b.y - a.y; + return Math.sqrt(dx * dx + dy * dy); +}; + +const getAttr = (distance: number, maxDist: number, minVal: number, maxVal: number) => { + const val = maxVal - Math.abs((maxVal * distance) / maxDist); + return Math.max(minVal, val + minVal); +}; + +const debounce = (func: (...args: any[]) => void, delay: number) => { + let timeoutId: NodeJS.Timeout; + return (...args: any[]) => { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => { + func.apply(this, args); + }, delay); + }; +}; + const TextPressure: React.FC = ({ text = 'Compressa', fontFamily = 'Compressa VF', @@ -50,12 +71,6 @@ const TextPressure: React.FC = ({ const chars = text.split(''); - const dist = (a: { x: number; y: number }, b: { x: number; y: number }) => { - const dx = b.x - a.x; - const dy = b.y - a.y; - return Math.sqrt(dx * dx + dy * dy); - }; - useEffect(() => { const handleMouseMove = (e: MouseEvent) => { cursorRef.current.x = e.clientX; @@ -68,7 +83,7 @@ const TextPressure: React.FC = ({ }; window.addEventListener('mousemove', handleMouseMove); - window.addEventListener('touchmove', handleTouchMove, { passive: false }); + window.addEventListener('touchmove', handleTouchMove, { passive: true }); if (containerRef.current) { const { left, top, width, height } = containerRef.current.getBoundingClientRect(); @@ -84,7 +99,7 @@ const TextPressure: React.FC = ({ }; }, []); - const setSize = () => { + const setSize = useCallback(() => { if (!containerRef.current || !titleRef.current) return; const { width: containerW, height: containerH } = containerRef.current.getBoundingClientRect(); @@ -106,13 +121,14 @@ const TextPressure: React.FC = ({ setLineHeight(yRatio); } }); - }; + }, [chars.length, minFontSize, scale]); useEffect(() => { - setSize(); - window.addEventListener('resize', setSize); - return () => window.removeEventListener('resize', setSize); - }, [scale, text]); + const debouncedSetSize = debounce(setSize, 100); + debouncedSetSize(); + window.addEventListener('resize', debouncedSetSize); + return () => window.removeEventListener('resize', debouncedSetSize); + }, [setSize]); useEffect(() => { let rafId: number; @@ -135,18 +151,19 @@ const TextPressure: React.FC = ({ const d = dist(mouseRef.current, charCenter); - const getAttr = (distance: number, minVal: number, maxVal: number) => { - const val = maxVal - Math.abs((maxVal * distance) / maxDist); - return Math.max(minVal, val + minVal); - }; + const wdth = width ? Math.floor(getAttr(d, maxDist, 5, 200)) : 100; + const wght = weight ? Math.floor(getAttr(d, maxDist, 100, 900)) : 400; + const italVal = italic ? getAttr(d, maxDist, 0, 1).toFixed(2) : '0'; + const alphaVal = alpha ? getAttr(d, maxDist, 0, 1).toFixed(2) : '1'; - const wdth = width ? Math.floor(getAttr(d, 5, 200)) : 100; - const wght = weight ? Math.floor(getAttr(d, 100, 900)) : 400; - const italVal = italic ? getAttr(d, 0, 1).toFixed(2) : '0'; - const alphaVal = alpha ? getAttr(d, 0, 1).toFixed(2) : '1'; + const newFontVariationSettings = `'wght' ${wght}, 'wdth' ${wdth}, 'ital' ${italVal}`; - span.style.opacity = alphaVal; - span.style.fontVariationSettings = `'wght' ${wght}, 'wdth' ${wdth}, 'ital' ${italVal}`; + if (span.style.fontVariationSettings !== newFontVariationSettings) { + span.style.fontVariationSettings = newFontVariationSettings; + } + if (alpha && span.style.opacity !== alphaVal) { + span.style.opacity = alphaVal; + } }); } @@ -155,10 +172,10 @@ const TextPressure: React.FC = ({ animate(); return () => cancelAnimationFrame(rafId); - }, [width, weight, italic, alpha, chars.length]); + }, [width, weight, italic, alpha]); - return ( -
+ const styleElement = useMemo(() => { + return ( + ); + }, [fontFamily, fontUrl, stroke, textColor, strokeColor, strokeWidth]); + return ( +
+ {styleElement}