Created
December 22, 2020 17:35
Revisions
-
queerviolet renamed this gist
Dec 22, 2020 . 1 changed file with 0 additions and 0 deletions.There are no files selected for viewing
File renamed without changes. -
queerviolet created this gist
Dec 22, 2020 .There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,149 @@ type Ratio = [number, number] type Size = {width: number, height: number} type Position = {top: number, left: number} type Frame = Position & {bottom: number, right: number} export type Box = Frame & Size export type Letterbox = Box & { bars: Box[], su: number } /** * Compute a letterbox and apply its measurements as CSS properties to document.body. * * This does not create any DOM elements or crop the page, but gives you the tools to do so in CSS. * * Applies the following CSS vars and keeps them up to date (the actual numbers here are just an example): * * --letterbox-top:26.3125px; * --letterbox-left:0px; * --letterbox-width:1590px; * --letterbox-height:894.375px; * --letterbox-bottom:26.3125px; * --letterbox-right:0px * --letterbox-bars-0-top:0px * --letterbox-bars-0-left:0px * --letterbox-bars-0-width:1590px * --letterbox-bars-0-height:26.3125px * --letterbox-bars-0-bottom:920.688px * --letterbox-bars-0-right:0px * --letterbox-bars-1-top:920.688px * --letterbox-bars-1-left:0px * --letterbox-bars-1-width:1590px * --letterbox-bars-1-height:26.3125px * --letterbox-bars-1-bottom:0px * --letterbox-bars-1-right:0px * --su:99.375px; * * `--letterbox-{top, left, width, height, bottom, right}` describes the stage box, where drawing should occur. * * `--letterbox-bars-{0,1}-{top, left, width, height, bottom, right}` describes the dead space (the "bars"), which * may be at the top and bottom (for landscape letterboxing) or left and right (for portait) of the stage. You might * use these measurements to position masks which sit over the page and decisively block any overdraw. The bars will * always be specified, but they might be zero width or height (if the page's aspect is exactly the letterbox aspect). * * `--su` is a new unit, "stage units", derived from the aspect ratio. Specifying an aspect of `[16, 9]` * results in `--su` being defined as `stageWidth / 16` (or as `stageHeight / 9`—they are the same number * by definition). * * `--su` can be used in CSS via `calc()`, e.g. `left: calc(var(--su) * 2)` * * @param aspect width and height in stage units * @param onReshape called with new bounds whenever the stage is reshaped * @returns a destroy function */ export default function applyLetterbox(aspect: Ratio = [16, 9], onReshape: (box: Box) => void = None) { function onResize() { const box = letterbox(aspect, {width: innerWidth, height: innerHeight}) setCSSPropertiesFrom(box) onReshape(box) } window.addEventListener('resize', onResize) onResize() return () => window.removeEventListener('resize', onResize) } export function letterbox(ratio: Ratio, container: Size): Letterbox { const [w, h] = ratio const aspect = w / h const containerAspect = container.width / container.height if (containerAspect > aspect) { // Container is flatter than content, lock to container // height and letterbox on left and right const width = aspect * container.height const left = (container.width - width) / 2 const height = container.height const top = 0 return letterboxFrom(ratio, container, { top, left, width, height, }) } // Container is taller than content, lock to container // width and letterbox on top and bottom const height = container.width / aspect const top = (container.height - height) / 2 const width = container.width const left = 0 return letterboxFrom(ratio, container, {top, left, width, height}) } const letterboxFrom = (ratio: Ratio, container: Size, box: Size & Position): Letterbox => ({ ...boxFrom(container, box), bars: subtract(container, box), su: box.width / ratio[0], }) const boxFrom = (container: Size, box: Size & Position): Box => ({ ...box, bottom: container.height - (box.top + box.height), right: container.width - (box.left + box.width), }) const subtract = (container: Size, box: Size & Position): Box[] => { if (!box.top) { const width = (container.width - box.width) / 2 return [ boxFrom(container, { top: 0, left: 0, width, height: container.height }), boxFrom(container, { top: 0, left: container.width - width, width, height: container.height }), ] } const height = (container.height - box.height) / 2 return [ boxFrom(container, { top: 0, left: 0, width: container.width, height, }), boxFrom(container, { top: container.height - height, left: 0, width: container.width, height, }) ] } const px = (px: number) => `${px}px` const None = () => {} const setCSSPropertiesFrom = (src: any, prefix='--letterbox-', element=document.body) => Object.keys(src).forEach(k => typeof src[k] === 'object' ? setCSSPropertiesFrom(src[k], prefix + k + '-', element) : typeof src[k] === 'number' ? element.style.setProperty(k !== 'su' ? prefix + k : `--${k}`, px(src[k])) : element.style.setProperty(prefix + k, src[k]) )