Bundle shaders into the build and add onFirstRender
- 셰이더 2종을 fetch 대신 번들 문자열로 임포트 (tsup --loader .glsl=text) - 기본 사용에서 네트워크 왕복 없이 즉시 준비 - 커스텀 셰이더 경로가 주어진 것만 fetch하고 나머지는 번들 소스 사용 - onFirstRender 콜백 추가 - 첫 텍스처 렌더 직후 1회 호출, 소비자가 스켈레톤 내리는 시점으로 쓴다 - 마운트마다 찍히던 디버그 console.log 제거 (v1.5.1 잔재 문구 포함) - v1.8.0
This commit is contained in:
parent
692f9b1260
commit
0ffa83df88
2
dist/index.d.mts
vendored
2
dist/index.d.mts
vendored
@ -350,6 +350,8 @@ interface ImageDistortionProps {
|
|||||||
mouseInteraction?: MouseInteractionConfig;
|
mouseInteraction?: MouseInteractionConfig;
|
||||||
/** 독립 스프라이트 이펙트 영역 (왜곡 영역과 분리) */
|
/** 독립 스프라이트 이펙트 영역 (왜곡 영역과 분리) */
|
||||||
spriteEffectAreas?: SpriteEffectArea[];
|
spriteEffectAreas?: SpriteEffectArea[];
|
||||||
|
/** 첫 텍스처 렌더가 화면에 그려진 직후 1회 호출 - 로딩 스켈레톤 내리는 타이밍용 */
|
||||||
|
onFirstRender?: () => void;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* GPU 가속 이미지 왜곡 컴포넌트
|
* GPU 가속 이미지 왜곡 컴포넌트
|
||||||
|
|||||||
2
dist/index.d.ts
vendored
2
dist/index.d.ts
vendored
@ -350,6 +350,8 @@ interface ImageDistortionProps {
|
|||||||
mouseInteraction?: MouseInteractionConfig;
|
mouseInteraction?: MouseInteractionConfig;
|
||||||
/** 독립 스프라이트 이펙트 영역 (왜곡 영역과 분리) */
|
/** 독립 스프라이트 이펙트 영역 (왜곡 영역과 분리) */
|
||||||
spriteEffectAreas?: SpriteEffectArea[];
|
spriteEffectAreas?: SpriteEffectArea[];
|
||||||
|
/** 첫 텍스처 렌더가 화면에 그려진 직후 1회 호출 - 로딩 스켈레톤 내리는 타이밍용 */
|
||||||
|
onFirstRender?: () => void;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* GPU 가속 이미지 왜곡 컴포넌트
|
* GPU 가속 이미지 왜곡 컴포넌트
|
||||||
|
|||||||
213
dist/index.js
vendored
213
dist/index.js
vendored
File diff suppressed because one or more lines are too long
2
dist/index.js.map
vendored
2
dist/index.js.map
vendored
File diff suppressed because one or more lines are too long
213
dist/index.mjs
vendored
213
dist/index.mjs
vendored
File diff suppressed because one or more lines are too long
2
dist/index.mjs.map
vendored
2
dist/index.mjs.map
vendored
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@baekryang/responsive-image-canvas",
|
"name": "@baekryang/responsive-image-canvas",
|
||||||
"version": "1.7.0",
|
"version": "1.8.0",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://git.bnovalab.com/api/packages/baekryang/npm/"
|
"registry": "https://git.bnovalab.com/api/packages/baekryang/npm/"
|
||||||
},
|
},
|
||||||
@ -19,8 +19,8 @@
|
|||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsup src/index.ts --format cjs,esm --dts",
|
"build": "tsup src/index.ts --format cjs,esm --dts --loader \".glsl=text\"",
|
||||||
"dev": "tsup src/index.ts --format cjs,esm --dts --watch"
|
"dev": "tsup src/index.ts --format cjs,esm --dts --watch --loader \".glsl=text\""
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^18.0.0 || ^19.0.0",
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
|||||||
@ -2,14 +2,23 @@ import React, { useEffect, useRef, useState, useCallback } from 'react';
|
|||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import {type DistortionArea, SpriteEffectArea} from '@/types';
|
import {type DistortionArea, SpriteEffectArea} from '@/types';
|
||||||
import { ThreeScene } from '@/engine/ThreeScene';
|
import { ThreeScene } from '@/engine/ThreeScene';
|
||||||
import { ShaderManager } from '@/engine/ShaderManager';
|
|
||||||
import { AnimationLoop } from '@/engine/AnimationLoop';
|
import { AnimationLoop } from '@/engine/AnimationLoop';
|
||||||
import { SpriteEffectManager } from '@/engine/SpriteEffectManager';
|
import { SpriteEffectManager } from '@/engine/SpriteEffectManager';
|
||||||
import { useAnimationFrame } from '@/hooks/useAnimationFrame';
|
import { useAnimationFrame } from '@/hooks/useAnimationFrame';
|
||||||
import { useMouseInteraction } from '@/hooks/useMouseInteraction';
|
import { useMouseInteraction } from '@/hooks/useMouseInteraction';
|
||||||
import { SHADER_CONFIG } from '@/utils/constants';
|
import { SHADER_CONFIG } from '@/utils/constants';
|
||||||
import { describeSrc } from '@/utils/describeSrc';
|
|
||||||
import { MouseInteractionConfig } from '@/types/interaction';
|
import { MouseInteractionConfig } from '@/types/interaction';
|
||||||
|
import bundledVertexShader from '@/shaders/distortion.vert.glsl';
|
||||||
|
import bundledFragmentShader from '@/shaders/distortion.frag.glsl';
|
||||||
|
|
||||||
|
/** 커스텀 셰이더 경로가 주어졌을 때만 쓰는 fetch 로더 */
|
||||||
|
async function fetchShaderSource(path: string): Promise<string> {
|
||||||
|
const response = await fetch(path);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`셰이더 로드 실패 (${path}): ${response.statusText}`);
|
||||||
|
}
|
||||||
|
return response.text();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ImageDistortion 컴포넌트 Props
|
* ImageDistortion 컴포넌트 Props
|
||||||
@ -31,6 +40,8 @@ export interface ImageDistortionProps {
|
|||||||
mouseInteraction?: MouseInteractionConfig;
|
mouseInteraction?: MouseInteractionConfig;
|
||||||
/** 독립 스프라이트 이펙트 영역 (왜곡 영역과 분리) */
|
/** 독립 스프라이트 이펙트 영역 (왜곡 영역과 분리) */
|
||||||
spriteEffectAreas?: SpriteEffectArea[];
|
spriteEffectAreas?: SpriteEffectArea[];
|
||||||
|
/** 첫 텍스처 렌더가 화면에 그려진 직후 1회 호출 - 로딩 스켈레톤 내리는 타이밍용 */
|
||||||
|
onFirstRender?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,11 +57,15 @@ export const ImageDistortion: React.FC<ImageDistortionProps> = ({
|
|||||||
className,
|
className,
|
||||||
mouseInteraction,
|
mouseInteraction,
|
||||||
spriteEffectAreas = [],
|
spriteEffectAreas = [],
|
||||||
|
onFirstRender,
|
||||||
}) => {
|
}) => {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const sceneRef = useRef<ThreeScene | null>(null);
|
const sceneRef = useRef<ThreeScene | null>(null);
|
||||||
const shaderManagerRef = useRef<ShaderManager>(new ShaderManager());
|
|
||||||
const textureRef = useRef<THREE.Texture | null>(null);
|
const textureRef = useRef<THREE.Texture | null>(null);
|
||||||
|
// 콜백을 ref로 들고 있어 렌더마다 바뀌어도 로드 effect가 재실행되지 않게 한다
|
||||||
|
const onFirstRenderRef = useRef(onFirstRender);
|
||||||
|
onFirstRenderRef.current = onFirstRender;
|
||||||
|
const firstRenderNotifiedRef = useRef(false);
|
||||||
const spriteManagerRef = useRef<SpriteEffectManager | null>(null);
|
const spriteManagerRef = useRef<SpriteEffectManager | null>(null);
|
||||||
const currentAreasRef = useRef<DistortionArea[]>(areas);
|
const currentAreasRef = useRef<DistortionArea[]>(areas);
|
||||||
|
|
||||||
@ -109,35 +124,35 @@ export const ImageDistortion: React.FC<ImageDistortionProps> = ({
|
|||||||
|
|
||||||
// Three.js 씬 초기화
|
// Three.js 씬 초기화
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('[ImageDistortion] useEffect 실행, containerRef.current:', containerRef.current);
|
if (!containerRef.current) return;
|
||||||
|
|
||||||
if (!containerRef.current) {
|
|
||||||
console.warn('[ImageDistortion] containerRef.current가 null입니다. 컴포넌트가 제대로 마운트되지 않았습니다.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[ImageDistortion] v1.5.1 초기화 시작');
|
|
||||||
const scene = new ThreeScene(containerRef.current);
|
const scene = new ThreeScene(containerRef.current);
|
||||||
sceneRef.current = scene;
|
sceneRef.current = scene;
|
||||||
|
|
||||||
// 셰이더 로드
|
let cancelled = false;
|
||||||
const vertPath = vertexShaderPath || '/shaders/distortion.vert.glsl';
|
const apply = (vertex: string, fragment: string) => {
|
||||||
const fragPath = fragmentShaderPath || '/shaders/distortion.frag.glsl';
|
if (cancelled) return;
|
||||||
|
scene.setShaderMaterial(vertex, fragment);
|
||||||
|
setIsReady(true);
|
||||||
|
};
|
||||||
|
|
||||||
console.log('[ImageDistortion] 셰이더 로드 시도:', { vertPath, fragPath });
|
if (!vertexShaderPath && !fragmentShaderPath) {
|
||||||
|
// 번들된 셰이더 - 네트워크 왕복 없이 즉시 준비된다
|
||||||
shaderManagerRef.current
|
apply(bundledVertexShader, bundledFragmentShader);
|
||||||
.loadShaders(vertPath, fragPath)
|
} else {
|
||||||
.then(({ vertex, fragment }) => {
|
// 커스텀 경로가 주어진 것만 fetch, 나머지는 번들 셰이더
|
||||||
console.log('[ImageDistortion] 셰이더 로드 성공');
|
Promise.all([
|
||||||
scene.setShaderMaterial(vertex, fragment);
|
vertexShaderPath ? fetchShaderSource(vertexShaderPath) : Promise.resolve(bundledVertexShader),
|
||||||
setIsReady(true);
|
fragmentShaderPath ? fetchShaderSource(fragmentShaderPath) : Promise.resolve(bundledFragmentShader),
|
||||||
})
|
])
|
||||||
.catch((error) => {
|
.then(([vertex, fragment]) => apply(vertex, fragment))
|
||||||
console.error('[ImageDistortion] 셰이더 로드 실패:', error);
|
.catch((error) => {
|
||||||
});
|
console.error('[ImageDistortion] 셰이더 로드 실패:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
scene.dispose();
|
scene.dispose();
|
||||||
if (textureRef.current) {
|
if (textureRef.current) {
|
||||||
textureRef.current.dispose();
|
textureRef.current.dispose();
|
||||||
@ -147,27 +162,14 @@ export const ImageDistortion: React.FC<ImageDistortionProps> = ({
|
|||||||
|
|
||||||
// 이미지 텍스처 로드
|
// 이미지 텍스처 로드
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!imageSrc || !isReady) {
|
if (!imageSrc || !isReady) return;
|
||||||
console.log('[ImageDistortion] 이미지 로드 스킵:', {
|
|
||||||
imageSrc: describeSrc(imageSrc),
|
|
||||||
isReady,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// describeSrc: data: URL을 그대로 찍으면 DevTools가 그 한 줄을 렌더하느라
|
|
||||||
// 메인 스레드가 수 초간 멈춘다. 너무 길면 길이만 남기고 본문은 버린다.
|
|
||||||
console.log('[ImageDistortion] 이미지 로드 시작:', describeSrc(imageSrc));
|
|
||||||
setImageLoaded(false);
|
setImageLoaded(false);
|
||||||
|
|
||||||
const loader = new THREE.TextureLoader();
|
const loader = new THREE.TextureLoader();
|
||||||
loader.load(
|
loader.load(
|
||||||
imageSrc,
|
imageSrc,
|
||||||
(texture) => {
|
(texture) => {
|
||||||
console.log('[ImageDistortion] 이미지 로드 성공!', {
|
|
||||||
width: texture.image.width,
|
|
||||||
height: texture.image.height
|
|
||||||
});
|
|
||||||
textureRef.current = texture;
|
textureRef.current = texture;
|
||||||
setImageLoaded(true);
|
setImageLoaded(true);
|
||||||
if (sceneRef.current) {
|
if (sceneRef.current) {
|
||||||
@ -175,14 +177,13 @@ export const ImageDistortion: React.FC<ImageDistortionProps> = ({
|
|||||||
u_texture: { value: texture },
|
u_texture: { value: texture },
|
||||||
});
|
});
|
||||||
sceneRef.current.render();
|
sceneRef.current.render();
|
||||||
console.log('[ImageDistortion] 텍스처 업데이트 및 렌더링 완료');
|
if (!firstRenderNotifiedRef.current) {
|
||||||
|
firstRenderNotifiedRef.current = true;
|
||||||
|
onFirstRenderRef.current?.();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(progress) => {
|
undefined,
|
||||||
console.log('[ImageDistortion] 이미지 로딩 중...',
|
|
||||||
Math.round((progress.loaded / progress.total) * 100) + '%'
|
|
||||||
);
|
|
||||||
},
|
|
||||||
(error) => {
|
(error) => {
|
||||||
console.error('[ImageDistortion] 이미지 로드 실패:', error);
|
console.error('[ImageDistortion] 이미지 로드 실패:', error);
|
||||||
setImageLoaded(false);
|
setImageLoaded(false);
|
||||||
|
|||||||
5
src/glsl.d.ts
vendored
Normal file
5
src/glsl.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// .glsl 임포트를 문자열로 취급 (tsup --loader .glsl=text와 한 벌)
|
||||||
|
declare module '*.glsl' {
|
||||||
|
const source: string;
|
||||||
|
export default source;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user