refactor: 얇게 남아있던 별의 꼬리가 사라지도록 수정

This commit is contained in:
2026-02-14 22:21:24 +09:00
parent 1ccb9e517a
commit 25a4ebd342
2 changed files with 54 additions and 16 deletions

View File

@@ -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 (
<canvas
ref={canvasRef}
className="fixed inset-0 z-0 bg-black pointer-events-none"
className="fixed inset-0 z-0 h-screen w-screen bg-black pointer-events-none"
/>
);
}

View File

@@ -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,