Seidenhintergrund im Dev-Modus reparieren, Resize-Warnung beheben
- SilkBackground: beim Aufräumen nur Programm/Buffer löschen statt den WebGL-Kontext zu verwerfen. React Strict Mode mountet im Dev-Modus zweimal, der zweite Mount bekam sonst einen verlorenen Kontext und der Hintergrund blieb leer. Kontextverlust/-wiederherstellung wird jetzt behandelt. - useFitText: nur auf Breitenänderungen reagieren und die Anpassung in den nächsten Frame verschieben, damit keine "ResizeObserver loop"-Fehler entstehen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KBHsCnWNXAB2Le3gAEsivF
This commit is contained in:
2 files changed
+84
-34
No files matched your search
@@ -106,37 +106,13 @@ export function SilkBackground({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const program = gl.createProgram()!
|
|
||||||
gl.attachShader(program, compile(gl, gl.VERTEX_SHADER, VERTEX))
|
|
||||||
gl.attachShader(program, compile(gl, gl.FRAGMENT_SHADER, FRAGMENT))
|
|
||||||
gl.linkProgram(program)
|
|
||||||
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
||||||
canvas.style.display = 'none'
|
|
||||||
return
|
|
||||||
}
|
|
||||||
gl.useProgram(program)
|
|
||||||
|
|
||||||
// Ein Dreieck, das den ganzen Viewport abdeckt
|
|
||||||
const buffer = gl.createBuffer()
|
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
|
|
||||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW)
|
|
||||||
const position = gl.getAttribLocation(program, 'position')
|
|
||||||
gl.enableVertexAttribArray(position)
|
|
||||||
gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0)
|
|
||||||
|
|
||||||
const u = (name: string) => gl.getUniformLocation(program, name)
|
|
||||||
const uTime = u('uTime')
|
|
||||||
const uAspect = u('uAspect')
|
|
||||||
const uPointer = u('uPointer')
|
|
||||||
gl.uniform3fv(u('uColor'), hexToRgb(color))
|
|
||||||
gl.uniform1f(u('uSpeed'), speed)
|
|
||||||
gl.uniform1f(u('uScale'), scale)
|
|
||||||
gl.uniform1f(u('uRotation'), rotation)
|
|
||||||
gl.uniform1f(u('uNoiseIntensity'), noiseIntensity)
|
|
||||||
|
|
||||||
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
||||||
// Seide ist weich — eine niedrigere Auflösung sieht gleich aus und spart Akku
|
// Seide ist weich — eine niedrigere Auflösung sieht gleich aus und spart Akku
|
||||||
const dpr = Math.min(window.devicePixelRatio || 1, 1.25)
|
const dpr = Math.min(window.devicePixelRatio || 1, 1.25)
|
||||||
|
let program: WebGLProgram | null = null
|
||||||
|
let buffer: WebGLBuffer | null = null
|
||||||
|
let uTime: WebGLUniformLocation | null = null
|
||||||
|
let uAspect: WebGLUniformLocation | null = null
|
||||||
|
let uPointer: WebGLUniformLocation | null = null
|
||||||
|
|
||||||
const resize = () => {
|
const resize = () => {
|
||||||
const { clientWidth: w, clientHeight: h } = canvas
|
const { clientWidth: w, clientHeight: h } = canvas
|
||||||
@@ -145,7 +121,47 @@ export function SilkBackground({
|
|||||||
gl.viewport(0, 0, canvas.width, canvas.height)
|
gl.viewport(0, 0, canvas.width, canvas.height)
|
||||||
gl.uniform1f(uAspect, w / Math.max(1, h))
|
gl.uniform1f(uAspect, w / Math.max(1, h))
|
||||||
}
|
}
|
||||||
resize()
|
|
||||||
|
// Programm und Geometrie anlegen — auch erneut, falls der Browser den WebGL-Kontext zurücksetzt
|
||||||
|
const setup = () => {
|
||||||
|
program = gl.createProgram()
|
||||||
|
const vs = compile(gl, gl.VERTEX_SHADER, VERTEX)
|
||||||
|
const fs = compile(gl, gl.FRAGMENT_SHADER, FRAGMENT)
|
||||||
|
gl.attachShader(program, vs)
|
||||||
|
gl.attachShader(program, fs)
|
||||||
|
gl.linkProgram(program)
|
||||||
|
gl.deleteShader(vs)
|
||||||
|
gl.deleteShader(fs)
|
||||||
|
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) return false
|
||||||
|
gl.useProgram(program)
|
||||||
|
|
||||||
|
// Ein Dreieck, das den ganzen Viewport abdeckt
|
||||||
|
buffer = gl.createBuffer()
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
|
||||||
|
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW)
|
||||||
|
const position = gl.getAttribLocation(program, 'position')
|
||||||
|
gl.enableVertexAttribArray(position)
|
||||||
|
gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0)
|
||||||
|
|
||||||
|
const u = (name: string) => gl.getUniformLocation(program!, name)
|
||||||
|
uTime = u('uTime')
|
||||||
|
uAspect = u('uAspect')
|
||||||
|
uPointer = u('uPointer')
|
||||||
|
gl.uniform3fv(u('uColor'), hexToRgb(color))
|
||||||
|
gl.uniform1f(u('uSpeed'), speed)
|
||||||
|
gl.uniform1f(u('uScale'), scale)
|
||||||
|
gl.uniform1f(u('uRotation'), rotation)
|
||||||
|
gl.uniform1f(u('uNoiseIntensity'), noiseIntensity)
|
||||||
|
resize()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!setup()) {
|
||||||
|
canvas.style.display = 'none'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
canvas.style.display = ''
|
||||||
|
|
||||||
const ro = new ResizeObserver(resize)
|
const ro = new ResizeObserver(resize)
|
||||||
ro.observe(canvas)
|
ro.observe(canvas)
|
||||||
|
|
||||||
@@ -157,8 +173,10 @@ export function SilkBackground({
|
|||||||
}
|
}
|
||||||
window.addEventListener('pointermove', onPointer, { passive: true })
|
window.addEventListener('pointermove', onPointer, { passive: true })
|
||||||
|
|
||||||
|
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||||
let raf = 0
|
let raf = 0
|
||||||
let visible = true
|
let visible = true
|
||||||
|
let lost = false
|
||||||
let elapsed = 0
|
let elapsed = 0
|
||||||
let last = performance.now()
|
let last = performance.now()
|
||||||
|
|
||||||
@@ -178,7 +196,7 @@ export function SilkBackground({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const start = () => {
|
const start = () => {
|
||||||
if (reduceMotion || raf || !visible || document.hidden) return
|
if (reduceMotion || raf || lost || !visible || document.hidden) return
|
||||||
last = performance.now()
|
last = performance.now()
|
||||||
raf = requestAnimationFrame(loop)
|
raf = requestAnimationFrame(loop)
|
||||||
}
|
}
|
||||||
@@ -200,13 +218,31 @@ export function SilkBackground({
|
|||||||
const onVisibility = () => (document.hidden ? stop() : start())
|
const onVisibility = () => (document.hidden ? stop() : start())
|
||||||
document.addEventListener('visibilitychange', onVisibility)
|
document.addEventListener('visibilitychange', onVisibility)
|
||||||
|
|
||||||
|
const onLost = (e: Event) => {
|
||||||
|
e.preventDefault() // erlaubt dem Browser, den Kontext wiederherzustellen
|
||||||
|
lost = true
|
||||||
|
stop()
|
||||||
|
}
|
||||||
|
const onRestored = () => {
|
||||||
|
lost = false
|
||||||
|
if (!setup()) return
|
||||||
|
draw(performance.now())
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
canvas.addEventListener('webglcontextlost', onLost)
|
||||||
|
canvas.addEventListener('webglcontextrestored', onRestored)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
stop()
|
stop()
|
||||||
ro.disconnect()
|
ro.disconnect()
|
||||||
io.disconnect()
|
io.disconnect()
|
||||||
window.removeEventListener('pointermove', onPointer)
|
window.removeEventListener('pointermove', onPointer)
|
||||||
document.removeEventListener('visibilitychange', onVisibility)
|
document.removeEventListener('visibilitychange', onVisibility)
|
||||||
gl.getExtension('WEBGL_lose_context')?.loseContext()
|
canvas.removeEventListener('webglcontextlost', onLost)
|
||||||
|
canvas.removeEventListener('webglcontextrestored', onRestored)
|
||||||
|
// Ressourcen freigeben, den Kontext aber behalten (React Strict Mode mountet im Dev-Modus zweimal)
|
||||||
|
gl.deleteBuffer(buffer)
|
||||||
|
gl.deleteProgram(program)
|
||||||
}
|
}
|
||||||
}, [color, speed, scale, rotation, noiseIntensity])
|
}, [color, speed, scale, rotation, noiseIntensity])
|
||||||
|
|
||||||
|
|||||||
+16
-2
@@ -22,9 +22,23 @@ export function useFitText<T extends HTMLElement>(max = 400) {
|
|||||||
|
|
||||||
fit()
|
fit()
|
||||||
document.fonts?.ready.then(fit)
|
document.fonts?.ready.then(fit)
|
||||||
const ro = new ResizeObserver(fit)
|
|
||||||
|
let frame = 0
|
||||||
|
let lastWidth = parent.clientWidth
|
||||||
|
const ro = new ResizeObserver(() => {
|
||||||
|
// Höhenänderungen entstehen durch die Schriftgröße selbst — nur auf neue Breiten reagieren
|
||||||
|
const width = parent.clientWidth
|
||||||
|
if (width === lastWidth) return
|
||||||
|
lastWidth = width
|
||||||
|
// außerhalb der Observer-Runde anpassen, sonst meldet der Browser eine Resize-Schleife
|
||||||
|
cancelAnimationFrame(frame)
|
||||||
|
frame = requestAnimationFrame(fit)
|
||||||
|
})
|
||||||
ro.observe(parent)
|
ro.observe(parent)
|
||||||
return () => ro.disconnect()
|
return () => {
|
||||||
|
cancelAnimationFrame(frame)
|
||||||
|
ro.disconnect()
|
||||||
|
}
|
||||||
}, [max])
|
}, [max])
|
||||||
|
|
||||||
return ref
|
return ref
|
||||||
|
|||||||
Reference in new issue
Block a user