canvas-utils.js (3846B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 /** 8 * Create 2 canvases and contexts for drawing onto, 1 main canvas, and 1 zoom 9 * canvas. The main canvas dimensions match the parent div, but the CSS can be 10 * transformed to be zoomed and dragged around (potentially creating a blurry 11 * canvas once zoomed in). The zoom canvas is a zoomed in section that matches 12 * the parent div's dimensions and is kept in place through CSS. A zoomed in 13 * view of the visualization is drawn onto this canvas, providing a crisp zoomed 14 * in view of the tree map. 15 */ 16 const { debounce } = require("resource://devtools/shared/debounce.js"); 17 const EventEmitter = require("resource://devtools/shared/event-emitter.js"); 18 19 const HTML_NS = "http://www.w3.org/1999/xhtml"; 20 const FULLSCREEN_STYLE = { 21 width: "100%", 22 height: "100%", 23 position: "absolute", 24 }; 25 26 /** 27 * Create the canvases, resize handlers, and return references to them all 28 */ 29 class Canvases extends EventEmitter { 30 /** 31 * 32 * @param {HTMLDivElement} parentEl 33 * @param {number} debounceRate 34 */ 35 constructor(parentEl, debounceRate) { 36 super(); 37 38 this.container = createContainingDiv(parentEl); 39 40 // This canvas contains all of the treemap 41 this.main = createCanvas(this.container, "main"); 42 // This canvas contains only the zoomed in portion, overlaying the main canvas 43 this.zoom = createCanvas(this.container, "zoom"); 44 45 this.removeHandlers = handleResizes(this, debounceRate); 46 } 47 /** 48 * Remove the handlers and elements 49 * 50 * @return {type} description 51 */ 52 destroy() { 53 this.removeHandlers(); 54 this.container.removeChild(this.main.canvas); 55 this.container.removeChild(this.zoom.canvas); 56 } 57 } 58 59 module.exports = Canvases; 60 61 /** 62 * Create the containing div 63 * 64 * @param {HTMLDivElement} parentEl 65 * @return {HTMLDivElement} 66 */ 67 function createContainingDiv(parentEl) { 68 const div = parentEl.ownerDocument.createElementNS(HTML_NS, "div"); 69 Object.assign(div.style, FULLSCREEN_STYLE); 70 parentEl.appendChild(div); 71 return div; 72 } 73 74 /** 75 * Create a canvas and context 76 * 77 * @param {HTMLDivElement} container 78 * @param {string} className 79 * @return {object} { canvas, ctx } 80 */ 81 function createCanvas(container, className) { 82 const window = container.ownerDocument.defaultView; 83 const canvas = container.ownerDocument.createElementNS(HTML_NS, "canvas"); 84 container.appendChild(canvas); 85 canvas.width = container.offsetWidth * window.devicePixelRatio; 86 canvas.height = container.offsetHeight * window.devicePixelRatio; 87 canvas.className = className; 88 89 Object.assign(canvas.style, FULLSCREEN_STYLE, { 90 pointerEvents: "none", 91 }); 92 93 const ctx = canvas.getContext("2d"); 94 95 return { canvas, ctx }; 96 } 97 98 /** 99 * Resize the canvases' resolutions, and fires out the onResize callback 100 * 101 * @param {HTMLDivElement} container 102 * @param {object} canvases 103 * @param {number} debounceRate 104 */ 105 function handleResizes(canvases, debounceRate) { 106 const { container, main, zoom } = canvases; 107 const window = container.ownerDocument.defaultView; 108 109 function resize() { 110 const width = container.offsetWidth * window.devicePixelRatio; 111 const height = container.offsetHeight * window.devicePixelRatio; 112 113 main.canvas.width = width; 114 main.canvas.height = height; 115 zoom.canvas.width = width; 116 zoom.canvas.height = height; 117 118 canvases.emit("resize"); 119 } 120 121 // Tests may not need debouncing 122 const debouncedResize = 123 debounceRate > 0 ? debounce(resize, debounceRate) : resize; 124 125 window.addEventListener("resize", debouncedResize); 126 resize(); 127 128 return function removeResizeHandlers() { 129 window.removeEventListener("resize", debouncedResize); 130 }; 131 }