Refactor code structure for improved readability and maintainability
All checks were successful
Deploy / deploy (push) Successful in 10m26s
All checks were successful
Deploy / deploy (push) Successful in 10m26s
This commit is contained in:
109
src/components/use-pixel-heat.ts
Normal file
109
src/components/use-pixel-heat.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* Hero'daki (TurkeyPixelMap) imleç ısı fırçasının ortak hook hali.
|
||||
* `data-px` işaretli rect'ler imleç yaklaşınca anında turuncuya ısınır,
|
||||
* uzaklaşınca yavaşça kendi rengine soğur (asimetrik zamanlama).
|
||||
*
|
||||
* SVG katmanları çoğunlukla pointer-events-none olduğundan imleç window
|
||||
* üzerinden izlenir ve viewBox koordinatına çevrilir. Beklenen veri
|
||||
* öznitelikleri: data-px, data-cx, data-cy, data-op (taban opaklık).
|
||||
*/
|
||||
|
||||
import { useEffect, type RefObject } from "react";
|
||||
import { animate, type AnimationPlaybackControls } from "motion";
|
||||
|
||||
const FINE_POINTER_QUERY = "(hover: hover) and (pointer: fine)";
|
||||
const HOT_COLOR = "#f97316"; // orange-500
|
||||
const HOT_OPACITY = 0.85;
|
||||
const HEAT_DURATION = 0.15; // ısınma: anlık geri bildirim
|
||||
const COOL_DURATION = 1.4; // soğuma: iz gibi yavaşça söner
|
||||
|
||||
type HeatTarget = {
|
||||
el: SVGRectElement;
|
||||
cx: number;
|
||||
cy: number;
|
||||
baseOpacity: number;
|
||||
};
|
||||
|
||||
export function usePixelHeat(
|
||||
svgRef: RefObject<SVGSVGElement | null>,
|
||||
{
|
||||
/** Fırça yarıçapı, SVG (viewBox) biriminde */
|
||||
radius = 34,
|
||||
}: { radius?: number } = {},
|
||||
) {
|
||||
useEffect(() => {
|
||||
const svg = svgRef.current;
|
||||
if (!svg) return;
|
||||
// Dokunmatik cihazlarda hover yanlış tetiklenir; efekti imleçli cihazlara sakla
|
||||
if (!window.matchMedia(FINE_POINTER_QUERY).matches) return;
|
||||
|
||||
const viewW = svg.viewBox.baseVal.width;
|
||||
if (!viewW) return;
|
||||
|
||||
const baseColor = getComputedStyle(svg).color;
|
||||
const pixels: HeatTarget[] = Array.from(
|
||||
svg.querySelectorAll<SVGRectElement>("[data-px]"),
|
||||
).map((el) => ({
|
||||
el,
|
||||
cx: Number(el.dataset.cx),
|
||||
cy: Number(el.dataset.cy),
|
||||
baseOpacity: Number(el.dataset.op),
|
||||
}));
|
||||
if (pixels.length === 0) return;
|
||||
|
||||
const hot = new Set<SVGRectElement>();
|
||||
const controls = new Map<SVGRectElement, AnimationPlaybackControls>();
|
||||
|
||||
const run = (
|
||||
el: SVGRectElement,
|
||||
to: { fill: string; fillOpacity: number },
|
||||
duration: number,
|
||||
) => {
|
||||
controls.get(el)?.stop();
|
||||
controls.set(el, animate(el, to, { duration, ease: "easeOut" }));
|
||||
};
|
||||
|
||||
const onMove = (e: PointerEvent) => {
|
||||
const rect = svg.getBoundingClientRect();
|
||||
if (rect.width === 0) return;
|
||||
const scale = rect.width / viewW;
|
||||
const pad = radius * scale;
|
||||
// İmleç fırça menzilinin tamamen dışındaysa ve sıcak piksel yoksa
|
||||
// hiç hesap yapma (sayfada birden çok örnek dinlediği için önemli)
|
||||
if (
|
||||
hot.size === 0 &&
|
||||
(e.clientX < rect.left - pad ||
|
||||
e.clientX > rect.right + pad ||
|
||||
e.clientY < rect.top - pad ||
|
||||
e.clientY > rect.bottom + pad)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const x = (e.clientX - rect.left) / scale;
|
||||
const y = (e.clientY - rect.top) / scale;
|
||||
for (const p of pixels) {
|
||||
const d2 = (p.cx - x) ** 2 + (p.cy - y) ** 2;
|
||||
const isHot = d2 <= radius * radius;
|
||||
if (isHot && !hot.has(p.el)) {
|
||||
hot.add(p.el);
|
||||
run(p.el, { fill: HOT_COLOR, fillOpacity: HOT_OPACITY }, HEAT_DURATION);
|
||||
} else if (!isHot && hot.has(p.el)) {
|
||||
hot.delete(p.el);
|
||||
run(
|
||||
p.el,
|
||||
{ fill: baseColor, fillOpacity: p.baseOpacity },
|
||||
COOL_DURATION,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("pointermove", onMove, { passive: true });
|
||||
return () => {
|
||||
window.removeEventListener("pointermove", onMove);
|
||||
controls.forEach((c) => c.stop());
|
||||
};
|
||||
}, [svgRef, radius]);
|
||||
}
|
||||
Reference in New Issue
Block a user