addons/measure/src/box-model/canvas.ts
/* eslint-disable no-param-reassign */
import global from 'global';
interface Size {
width: number;
height: number;
}
interface CanvasState {
canvas?: HTMLCanvasElement;
context?: CanvasRenderingContext2D;
width?: number;
height?: number;
}
function getDocumentWidthAndHeight() {
const container = global.document.documentElement;
const height = Math.max(container.scrollHeight, container.offsetHeight);
const width = Math.max(container.scrollWidth, container.offsetWidth);
return { width, height };
}
function createCanvas(): CanvasState {
const canvas = global.document.createElement('canvas');
canvas.id = 'storybook-addon-measure';
const context = canvas.getContext('2d');
// Set canvas width & height
const { width, height } = getDocumentWidthAndHeight();
setCanvasWidthAndHeight(canvas, context, { width, height });
// Position canvas
canvas.style.position = 'absolute';
canvas.style.left = '0';
canvas.style.top = '0';
canvas.style.zIndex = '2147483647';
// Disable any user interactions
canvas.style.pointerEvents = 'none';
global.document.body.appendChild(canvas);
return { canvas, context, width, height };
}
function setCanvasWidthAndHeight(
canvas: HTMLCanvasElement,
context: CanvasRenderingContext2D,
{ width, height }: Size
) {
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
// Scale
const scale = global.window.devicePixelRatio;
canvas.width = Math.floor(width * scale);
canvas.height = Math.floor(height * scale);
// Normalize coordinate system to use css pixels.
context.scale(scale, scale);
}
let state: CanvasState = {};
export function init() {
if (!state.canvas) {
state = createCanvas();
}
}
export function clear() {
if (state.context) {
state.context.clearRect(0, 0, state.width, state.height);
}
}
export function draw(callback: (context: CanvasRenderingContext2D) => void) {
clear();
callback(state.context);
}
export function rescale() {
// First reset so that the canvas size doesn't impact the container size
setCanvasWidthAndHeight(state.canvas, state.context, { width: 0, height: 0 });
const { width, height } = getDocumentWidthAndHeight();
setCanvasWidthAndHeight(state.canvas, state.context, { width, height });
// update state
state.width = width;
state.height = height;
}
export function destroy() {
if (state.canvas) {
clear();
state.canvas.parentNode.removeChild(state.canvas);
state = {};
}
}