diff --git a/src/music/features/visualizer/Renderer.ts b/src/music/features/visualizer/Renderer.ts
index f5648c9..3597b59 100644
--- a/src/music/features/visualizer/Renderer.ts
+++ b/src/music/features/visualizer/Renderer.ts
@@ -19,6 +19,7 @@ class Renderer {
private lastFrameTime: number;
private dTime: number;
+ private nextAnimationFrame?: number;
private rotation: number;
@@ -53,13 +54,63 @@ class Renderer {
this.buffers = {};
}
- resize() {
- this.canvas.width = this.canvas.parentElement!.clientWidth;
- this.canvas.height = this.canvas.parentElement!.clientHeight;
+ resizeAndDraw(
+ gl: WebGL2RenderingContext,
+ shader: Shader,
+ observerData: ResizeObserverEntry | null
+ ) {
+ if (this.canvas.parentElement === null) {
+ throw new Error("renderer has been removed from dom");
+ }
+
+ if (this.nextAnimationFrame) {
+ cancelAnimationFrame(this.nextAnimationFrame);
+ }
+
+ // Note: For this to work, it's *incredibly important* for the
+ // canvas to be overflowable by its parent, and its parent to
+ // have `overflow: hidden` set. If using a flexbox, this means
+ // that the canvas has to be `position: absolute`.
+ let width: number;
+ let height: number;
+
+ if (observerData !== null && observerData.devicePixelContentBoxSize) {
+ width = observerData.devicePixelContentBoxSize[0].inlineSize;
+ height = observerData.devicePixelContentBoxSize[0].blockSize;
+ } else {
+ // Fallback; the above API is even newer than
+ // ResizeObserver, and by setting the observerData to null
+ // we can manually resize at least once without going
+ // through the API.
+ if (this.canvas.parentElement === null) {
+ throw new Error("canvas parent disappeared");
+ }
+ // Note: This *requires* `box-sizing: border-box`
+ ({ width, height } =
+ this.canvas.parentElement.getBoundingClientRect());
+ }
+
+ this.canvas.width = width;
+ this.canvas.height = height;
+
+ gl.viewport(0, 0, this.canvas.clientWidth, this.canvas.clientHeight);
+ this.updateProjection(gl, shader);
+
+ // ResizeObserver will call when we should draw, so do our own
+ // time calculation and draw the scene.
+ this.updateTime(performance.now());
+ this.drawScene(gl, shader);
+ }
+
+ updateTime(time: number) {
+ this.dTime = time - this.lastFrameTime;
+ this.lastFrameTime = time;
}
initializeScene() {
- this.resize();
+ if (this.canvas.parentElement === null) {
+ throw new Error("canvas was not added to page");
+ }
const gl = this.canvas.getContext("webgl2");
if (gl === null) {
@@ -78,12 +129,30 @@ class Renderer {
.build();
this.initGL(gl, shader);
- this.initMatrices(gl, shader);
+ this.updateProjection(gl, shader);
this.initBuffers(gl);
- this.drawScene(gl, shader);
+
+ try {
+ const observer = new ResizeObserver((elements) => {
+ // We only observe one element
+ const element = elements[0];
+ this.resizeAndDraw(gl, shader, element);
+ });
+ observer.observe(this.canvas.parentElement);
+ } catch (error) {
+ // If the browser does not support ResizeObserver, we
+ // simply don't resize. Resizing is hard enough, just use
+ // a modern browser.
+ if (error instanceof ReferenceError) {
+ console.warn(
+ "Browser does not support `ResizeObserver`. Canvas resizing will be disabled."
+ );
+ } else throw error;
+ }
+ this.resizeAndDraw(gl, shader, null);
}
- initMatrices(gl: WebGLRenderingContext, shader: Shader) {
+ updateProjection(gl: WebGLRenderingContext, shader: Shader) {
const projectionMatrix = mat4.create();
mat4.perspective(
projectionMatrix,
@@ -264,9 +333,8 @@ class Renderer {
this.overlay.innerText = `${dTime}ms (${cpuTime}ms / ${gpuTime}ms)`;
}
- requestAnimationFrame((time) => {
- this.dTime = time - this.lastFrameTime;
- this.lastFrameTime = time;
+ this.nextAnimationFrame = requestAnimationFrame((time) => {
+ this.updateTime(time);
this.drawScene(gl, shader);
});
diff --git a/src/music/features/visualizer/Visualizer.tsx b/src/music/features/visualizer/Visualizer.tsx
index c45228e..8081d6b 100644
--- a/src/music/features/visualizer/Visualizer.tsx
+++ b/src/music/features/visualizer/Visualizer.tsx
@@ -101,7 +101,13 @@ function Visualizer({
ref={visualizer}
className="is-flex-grow-1 is-clipped is-relative"
>
-
+