🍳 Zoom

🍳 and 🔍. Space + drag to pan, Z to zoom, and shift + Z to reset

  1. // ==UserScript==
  2. // @name 🍳 Zoom
  3. // @namespace https://greasyfork.org/users/281093
  4. // @match https://sketchful.io/
  5. // @grant none
  6. // @version 1.2
  7. // @author Bell
  8. // @description 🍳 and 🔍. Space + drag to pan, Z to zoom, and shift + Z to reset
  9. // @run-at body-end
  10. // ==/UserScript==
  11.  
  12. // Options
  13. const options = {
  14. onlyFreedraw: true,
  15. resetOnZoomOut: true
  16. }
  17.  
  18. //////
  19. const canvas = document.querySelector('#canvas');
  20. const zoomContainer = document.createElement('div');
  21. const sheet =
  22. window.document.styleSheets[window.document.styleSheets.length - 1];
  23. const resetObserver = new MutationObserver(resetCanvas);
  24. const panContainer = document.createElement('div');
  25.  
  26. let panning = false;
  27. const startPos = {};
  28. let canvasOffset = {};
  29. let zoomed = false;
  30.  
  31. (function init() {
  32. panContainer.style.position = 'relative';
  33. panContainer.style.transition = 'transform 0.1s ease';
  34. zoomContainer.style.overflow = 'hidden';
  35. zoomContainer.id = 'zoomContainer';
  36. sheet.insertRule('.dark #zoomContainer { background: #2C2F33}');
  37. sheet.insertRule('#zoomContainer { background: #c8c8c8}');
  38.  
  39. wrap(canvas, zoomContainer);
  40. wrap(canvas, panContainer);
  41. resetCanvas();
  42.  
  43. resetObserver.observe(document.querySelector('#gameSticky'), {
  44. childList: true,
  45. });
  46. document.addEventListener('keydown', keyDown, false);
  47. document.addEventListener('keyup', stopPanning, false);
  48. zoomContainer.addEventListener('pointerdown', getStartPos, true);
  49. zoomContainer.addEventListener('pointermove', moveCanvas, true);
  50. document.addEventListener('pointerup', stopMoving, false);
  51. })();
  52.  
  53. function wrap(toWrap, wrapper) {
  54. toWrap.parentNode.appendChild(wrapper);
  55. wrapper.appendChild(toWrap);
  56. }
  57.  
  58. function regenerateCursor() {
  59. const selectedTool = document.querySelector('.gameToolsSelected');
  60. selectedTool.id === 'gameToolsDraw'
  61. ? selectedTool.nextSibling.click()
  62. : selectedTool.previousSibling.click();
  63. selectedTool.click();
  64. }
  65.  
  66. function stopPanning(e) {
  67. if (e.code !== 'Space' || !panning) return;
  68. e.preventDefault();
  69. regenerateCursor();
  70. zoomContainer.style.cursor = 'default';
  71. panning = false;
  72. }
  73.  
  74. function keyDown(e) {
  75. if (!isDrawing() || !isFreeDraw() && options.onlyFreedraw) return;
  76. switch (e.code) {
  77. case 'Space':
  78. if (e.shiftKey) return;
  79. startPanning();
  80. e.preventDefault();
  81. break;
  82. case 'KeyZ':
  83. if (e.ctrlKey) {
  84. return;
  85. }
  86. else if (e.shiftKey && !options.resetOnZoomOut) {
  87. resetCanvas();
  88. e.stopImmediatePropagation();
  89. return;
  90. }
  91. toggleZoom();
  92. e.stopImmediatePropagation();
  93. }
  94. }
  95.  
  96. function toggleZoom() {
  97. panContainer.style.transform = zoomed ? '' : 'scale(2)';
  98. if (zoomed && options.resetOnZoomOut) resetCanvas();
  99. else zoomed = !zoomed;
  100. }
  101.  
  102. function startPanning() {
  103. if (panning || !zoomed && options.resetOnZoomOut) return;
  104. canvas.style.cursor = 'grab';
  105. zoomContainer.style.cursor = 'grab';
  106. panning = true;
  107. }
  108.  
  109. function moveCanvas(e) {
  110. if (!panning || !e.buttons) return;
  111. const offset = {};
  112. offset.x = e.clientX - startPos.x;
  113. offset.y = e.clientY - startPos.y;
  114. panContainer.style.left = `${canvasOffset.x + offset.x}px`;
  115. panContainer.style.top = `${canvasOffset.y + offset.y}px`;
  116. e.preventDefault();
  117. e.stopImmediatePropagation();
  118. }
  119.  
  120. function getStartPos(e) {
  121. if (!panning) return;
  122. startPos.x = e.clientX;
  123. startPos.y = e.clientY;
  124. canvas.style.cursor = 'grabbing';
  125. zoomContainer.style.cursor = 'grabbing';
  126. e.preventDefault();
  127. e.stopImmediatePropagation();
  128. }
  129.  
  130. function stopMoving(e) {
  131. canvasOffset.x = parseInt(panContainer.style.left.match(/-?\d+/)[0]);
  132. canvasOffset.y = parseInt(panContainer.style.top.match(/-?\d+/)[0]);
  133. if (panning) {
  134. canvas.style.cursor = 'grab';
  135. zoomContainer.style.cursor = 'grab';
  136. e.stopImmediatePropagation();
  137. }
  138. }
  139.  
  140. function resetCanvas() {
  141. panContainer.style.left = '0px';
  142. panContainer.style.top = '0px';
  143. panContainer.style.transform = '';
  144. zoomed = false;
  145. canvasOffset = { x: 0, y: 0 };
  146. }
  147.  
  148. function isDrawing() {
  149. return (
  150. document.querySelector('#gameTools').style.display !== 'none' &&
  151. document.querySelector('body > div.game').style.display !== 'none' &&
  152. document.activeElement !== document.querySelector('#gameChatInput')
  153. );
  154. }
  155.  
  156. function isFreeDraw() {
  157. return (
  158. canvas.style.display !== 'none' &&
  159. document.querySelector('#gameClock').style.display === 'none' &&
  160. document.querySelector('#gameSettings').style.display === 'none'
  161. );
  162. }