From 25a4ebd34226361cdae6ea15f5a00881e55e98d1 Mon Sep 17 00:00:00 2001 From: corpi Date: Sat, 14 Feb 2026 22:21:24 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20=EC=96=87=EA=B2=8C=20=EB=82=A8?= =?UTF-8?q?=EC=95=84=EC=9E=88=EB=8D=98=20=EB=B3=84=EC=9D=98=20=EA=BC=AC?= =?UTF-8?q?=EB=A6=AC=EA=B0=80=20=EC=82=AC=EB=9D=BC=EC=A7=80=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/FlightStarfieldCanvas.tsx | 64 ++++++++++++++----- src/shared/config/starfield.ts | 6 ++ 2 files changed, 54 insertions(+), 16 deletions(-) diff --git a/src/features/flight-starfield/ui/FlightStarfieldCanvas.tsx b/src/features/flight-starfield/ui/FlightStarfieldCanvas.tsx index 5a89a84..ac4fcd9 100644 --- a/src/features/flight-starfield/ui/FlightStarfieldCanvas.tsx +++ b/src/features/flight-starfield/ui/FlightStarfieldCanvas.tsx @@ -13,6 +13,7 @@ import { shouldRecycleFlightStar, } from "@/features/flight-starfield/model/starfieldModel"; import { FlightStar } from "@/features/flight-starfield/model/types"; +import { FLIGHT_STARFIELD_TUNING } from "@/shared/config/starfield"; import { clamp } from "@/shared/lib/math/number"; import { getPrefersReducedMotionMediaQuery } from "@/shared/lib/motion/prefersReducedMotion"; @@ -39,6 +40,7 @@ export function FlightStarfieldCanvas({ let width = window.innerWidth; let height = window.innerHeight; let animationFrameId = 0; + let dpr = 1; const motionQuery = getPrefersReducedMotionMediaQuery(); let prefersReducedMotion = motionQuery.matches; @@ -48,12 +50,21 @@ export function FlightStarfieldCanvas({ const vanishXJitter = vanishXJitterRef.current ?? 0; const setCanvasSize = () => { - width = window.innerWidth; - height = window.innerHeight; - canvas.width = width; - canvas.height = height; + const viewportWidth = window.visualViewport?.width ?? window.innerWidth; + const viewportHeight = window.visualViewport?.height ?? window.innerHeight; + width = Math.max(1, Math.floor(viewportWidth)); + height = Math.max(1, Math.floor(viewportHeight)); + canvas.style.width = `${width}px`; + canvas.style.height = `${height}px`; + dpr = window.devicePixelRatio || 1; + canvas.width = Math.max(1, Math.floor(width * dpr)); + canvas.height = Math.max(1, Math.floor(height * dpr)); + context.setTransform(dpr, 0, 0, dpr, 0, 0); + context.imageSmoothingEnabled = false; }; + const snapToDevicePixel = (value: number) => Math.round(value * dpr) / dpr; + const getVanishingPoint = () => createVanishingPoint({ width, @@ -102,12 +113,20 @@ export function FlightStarfieldCanvas({ const deltaX = toX - fromX; const deltaY = toY - fromY; const movementLength = Math.hypot(deltaX, deltaY); + const tailLineWidth = clamp(star.radius * 0.9, 0.65, 1.6); + const canDrawTail = + star.tailLength >= FLIGHT_STARFIELD_TUNING.tail.cleanup.minTailLengthToDraw && + movementLength >= FLIGHT_STARFIELD_TUNING.tail.cleanup.minMovementToDraw && + visibleAlpha >= FLIGHT_STARFIELD_TUNING.tail.cleanup.minAlphaToDraw && + tailLineWidth >= FLIGHT_STARFIELD_TUNING.tail.cleanup.minLineWidthToDraw; - if (star.tailLength < 1 || movementLength < 0.001) { + if (!canDrawTail) { + const snappedToX = snapToDevicePixel(toX); + const snappedToY = snapToDevicePixel(toY); context.globalAlpha = visibleAlpha; context.fillStyle = "#f8fbff"; context.beginPath(); - context.arc(toX, toY, star.radius, 0, Math.PI * 2); + context.arc(snappedToX, snappedToY, star.radius, 0, Math.PI * 2); context.fill(); context.globalAlpha = 1; return; @@ -115,26 +134,33 @@ export function FlightStarfieldCanvas({ const directionX = deltaX / movementLength; const directionY = deltaY / movementLength; - const tailX = toX - directionX * star.tailLength; - const tailY = toY - directionY * star.tailLength; + const tailX = snapToDevicePixel(toX - directionX * star.tailLength); + const tailY = snapToDevicePixel(toY - directionY * star.tailLength); + const snappedToX = snapToDevicePixel(toX); + const snappedToY = snapToDevicePixel(toY); - const gradient = context.createLinearGradient(tailX, tailY, toX, toY); + const gradient = context.createLinearGradient( + tailX, + tailY, + snappedToX, + snappedToY, + ); gradient.addColorStop(0, "rgba(248, 251, 255, 0)"); gradient.addColorStop(1, `rgba(248, 251, 255, ${visibleAlpha})`); context.strokeStyle = gradient; - context.lineWidth = clamp(star.radius * 0.9, 0.65, 1.6); + context.lineWidth = tailLineWidth; context.beginPath(); context.moveTo(tailX, tailY); - context.lineTo(toX, toY); + context.lineTo(snappedToX, snappedToY); context.stroke(); context.globalAlpha = Math.min(1, visibleAlpha + 0.08); context.fillStyle = "#f8fbff"; context.beginPath(); context.arc( - toX, - toY, + snappedToX, + snappedToY, clamp(star.radius * 0.72, 0.6, 1.45), 0, Math.PI * 2, @@ -177,7 +203,12 @@ export function FlightStarfieldCanvas({ }; const drawFrame = (moveStars: boolean) => { - context.fillStyle = "rgba(2, 5, 10, 0.3)"; + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = "source-over"; + context.clearRect(0, 0, canvas.width, canvas.height); + context.setTransform(dpr, 0, 0, dpr, 0, 0); + context.fillStyle = "rgb(2, 5, 10)"; context.fillRect(0, 0, width, height); drawCenterVeil(); @@ -235,7 +266,6 @@ export function FlightStarfieldCanvas({ }; const renderStatic = () => { - context.clearRect(0, 0, width, height); drawFrame(false); }; @@ -264,6 +294,7 @@ export function FlightStarfieldCanvas({ }; window.addEventListener("resize", handleResize); + window.visualViewport?.addEventListener("resize", handleResize); motionQuery.addEventListener("change", handleMotionChange); if (prefersReducedMotion || isPaused) { @@ -274,6 +305,7 @@ export function FlightStarfieldCanvas({ return () => { window.removeEventListener("resize", handleResize); + window.visualViewport?.removeEventListener("resize", handleResize); motionQuery.removeEventListener("change", handleMotionChange); stopAnimation(); }; @@ -282,7 +314,7 @@ export function FlightStarfieldCanvas({ return ( ); } diff --git a/src/shared/config/starfield.ts b/src/shared/config/starfield.ts index d870a43..6dd5a71 100644 --- a/src/shared/config/starfield.ts +++ b/src/shared/config/starfield.ts @@ -17,6 +17,12 @@ export const FLIGHT_STARFIELD_TUNING = { pointRange: { min: 0, max: 2.5 }, shortRange: { min: 2.5, max: 3.8 }, longRange: { min: 4, max: 10 }, + cleanup: { + minAlphaToDraw: 0.09, + minMovementToDraw: 0.18, + minLineWidthToDraw: 1, + minTailLengthToDraw: 1, + }, }, spawnRadius: { centerChance: 0.08,