Add downscale option

main
Shad Amethyst 2 years ago
parent d6fa38f9af
commit 096a0ebe64

@ -14,29 +14,32 @@ export const MAX_CANVAS_SIZE = 10000;
* the dimensions of the canvas. * the dimensions of the canvas.
* @prop {AttachCanvasUpdate=} onResize - A callback function which will be called whenever the * @prop {AttachCanvasUpdate=} onResize - A callback function which will be called whenever the
* dimensions of the canvas change. * dimensions of the canvas change.
* @prop {number=} downscale - An integer to downscale the canvas by,
* can be used to render the canvas at a lower resolution.
**/ **/
/** /**
* A function to call to unbind the event listeners attached to a canvas through `attachCanvas`.
* @callback AttachCanvasUnbind * @callback AttachCanvasUnbind
* @returns {void} * @returns {void}
**/ **/
/** /**
* @typedef {object} AttachCanvas * Makes the given canvas pixel-perfect, by dynamically resizing it
* @prop {AttachCanvasUnbind} unbind - Call this function to remove the event listeners attached * in order to have the canvas pixel resolution be always equal to the device resolution.
* to the canvas *
**/ * Returns a function to "unbind" the bound listeners.
*
/**
* @param {HTMLCanvasElement} canvas * @param {HTMLCanvasElement} canvas
* @param {AttachCanvasOptions} options * @param {AttachCanvasOptions} options
* @returns {HTMLCanvasElement & AttachCanvas} * @returns {AttachCanvasUnbind}
**/ **/
export function attachCanvas(canvas, options = {}) { export function attachCanvas(canvas, options = {}) {
const EPSILON = 0.001; const EPSILON = 0.001;
canvas.style.imageRendering = "pixelated"; canvas.style.imageRendering = "pixelated";
const container = options.container ?? canvas.parentNode; const container = options.container ?? canvas.parentNode;
const onResize = options.onResize ?? (() => {}); const onResize = options.onResize ?? (() => {});
const downscale = options.downscale ?? 1;
let old_width = null; let old_width = null;
let old_height = null; let old_height = null;
@ -50,8 +53,8 @@ export function attachCanvas(canvas, options = {}) {
}; };
// Compute native dimensions of canvas // Compute native dimensions of canvas
width = width * dpr; width = width * dpr / downscale;
height = height * dpr; height = height * dpr / downscale;
if (isInteger(width, EPSILON) && isInteger(height, EPSILON)) { if (isInteger(width, EPSILON) && isInteger(height, EPSILON)) {
width = Math.round(width); width = Math.round(width);
height = Math.round(height); height = Math.round(height);
@ -82,8 +85,8 @@ export function attachCanvas(canvas, options = {}) {
canvas.height = height; canvas.height = height;
// Set style width to guarantee perfect scaling // Set style width to guarantee perfect scaling
canvas.style.width = `${width / dpr}px`; canvas.style.width = `${width / dpr * downscale}px`;
canvas.style.height = `${height / dpr}px`; canvas.style.height = `${height / dpr * downscale}px`;
onResize(canvas, width, height); onResize(canvas, width, height);
} }
@ -106,7 +109,7 @@ export function attachCanvas(canvas, options = {}) {
resize(window.devicePixelRatio ?? 1); resize(window.devicePixelRatio ?? 1);
}, 0); }, 0);
canvas.unbind = function unbind() { return function unbind() {
if (resize_observer) { if (resize_observer) {
resize_observer.disconnect(); resize_observer.disconnect();
// Drop resize_observer // Drop resize_observer
@ -119,8 +122,6 @@ export function attachCanvas(canvas, options = {}) {
unbind_dpr = null; unbind_dpr = null;
} }
}; };
return canvas;
} }
export function listenPixelRatio(element, callback, fire_first = false) { export function listenPixelRatio(element, callback, fire_first = false) {

@ -1,5 +1,5 @@
import { JSX, Component, createEffect, onCleanup } from "solid-js"; import { JSX, Component, createEffect, onCleanup } from "solid-js";
import { attachCanvas } from '../index'; import { attachCanvas, AttachCanvasOptions } from '../index';
import styles from './PixelPerfectCanvas.module.css'; import styles from './PixelPerfectCanvas.module.css';
export type PixelPerfectCanvasProps = { export type PixelPerfectCanvasProps = {
@ -28,7 +28,7 @@ export type PixelPerfectCanvasProps = {
* Callback called after the event listeners for dimension and DPR changes are detached. * Callback called after the event listeners for dimension and DPR changes are detached.
**/ **/
onDetach?: (canvas: HTMLCanvasElement) => void, onDetach?: (canvas: HTMLCanvasElement) => void,
} } & Omit<AttachCanvasOptions, 'onResize' | 'container'>
/** /**
* A canvas that is wrapped in a container (which will be styled with `style` or `class`), * A canvas that is wrapped in a container (which will be styled with `style` or `class`),
@ -43,20 +43,19 @@ export const PixelPerfectCanvas: Component<PixelPerfectCanvasProps> = (props) =>
let canvasRef: HTMLCanvasElement; let canvasRef: HTMLCanvasElement;
let containerRef: HTMLDivElement; let containerRef: HTMLDivElement;
createEffect<ReturnType<typeof attachCanvas>>((old) => { createEffect(() => {
const result = attachCanvas(canvasRef, { const unbind = attachCanvas(canvasRef, {
container: containerRef, container: containerRef,
onResize: props.onResize, onResize: props.onResize,
downscale: props.downscale
}); });
props.onAttach?.(result); props.onAttach?.(canvasRef);
onCleanup(() => { onCleanup(() => {
result.unbind(); unbind();
props.onDetach?.(result); props.onDetach?.(canvasRef);
}); });
return result;
}); });
return (<div return (<div

@ -38,6 +38,7 @@ const App: Component = () => {
}} }}
onResize={update} onResize={update}
onAttach={setCanvasRef} onAttach={setCanvasRef}
downscale={8}
/> />
<input <input
type="checkbox" type="checkbox"

@ -21,6 +21,10 @@ export default defineConfig({
} }
}, },
rollupOptions: { rollupOptions: {
output: {
sourcemap: true,
format: 'iife',
},
external: ["solid-js"], external: ["solid-js"],
plugins: [] plugins: []
} }

Loading…
Cancel
Save