Spacing.js

A JavaScript utility for measuring the spacing between elements on webpage.

  1. // ==UserScript==
  2. // @name Spacing.js
  3. // @namespace https://github.com/stevenlei
  4. // @version 1.0.5
  5. // @description A JavaScript utility for measuring the spacing between elements on webpage.
  6. // @author Steven Lei <contact@stevenlei.com>
  7. // @run-at document-start
  8. // @include *
  9. // @license MIT
  10. // ==/UserScript==
  11. 'use strict';
  12. /*!
  13. * Spacing.js v1.0.5
  14. * Copyright (c) 2021 Steven Lei
  15. * Released under the MIT License.
  16. */
  17. /******/ (() => { // webpackBootstrap
  18. /******/ "use strict";
  19. /******/ // The require scope
  20. /******/ var __webpack_require__ = {};
  21. /******/
  22. /************************************************************************/
  23. /******/ /* webpack/runtime/make namespace object */
  24. /******/ (() => {
  25. /******/ // define __esModule on exports
  26. /******/ __webpack_require__.r = (exports) => {
  27. /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
  28. /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
  29. /******/ }
  30. /******/ Object.defineProperty(exports, '__esModule', { value: true });
  31. /******/ };
  32. /******/ })();
  33. /******/
  34. /************************************************************************/
  35. var __webpack_exports__ = {};
  36. // ESM COMPAT FLAG
  37. __webpack_require__.r(__webpack_exports__);
  38.  
  39. ;// CONCATENATED MODULE: ./src/rect.ts
  40. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  41.  
  42. function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
  43.  
  44. function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
  45.  
  46. var Rect = /*#__PURE__*/function () {
  47. function Rect(rect) {
  48. _classCallCheck(this, Rect);
  49.  
  50. this.top = rect.top;
  51. this.left = rect.left;
  52. this.width = rect.width;
  53. this.height = rect.height;
  54. this.right = rect.right;
  55. this.bottom = rect.bottom;
  56. }
  57.  
  58. _createClass(Rect, [{
  59. key: "colliding",
  60. value: function colliding(other) {
  61. return !(this.top > other.bottom || this.right < other.left || this.bottom < other.top || this.left > other.right);
  62. }
  63. }, {
  64. key: "containing",
  65. value: function containing(other) {
  66. return this.left <= other.left && other.left < this.width && this.top <= other.top && other.top < this.height;
  67. }
  68. }, {
  69. key: "inside",
  70. value: function inside(other) {
  71. return other.top <= this.top && this.top <= other.bottom && other.top <= this.bottom && this.bottom <= other.bottom && other.left <= this.left && this.left <= other.right && other.left <= this.right && this.right <= other.right;
  72. }
  73. }]);
  74.  
  75. return Rect;
  76. }();
  77.  
  78.  
  79. ;// CONCATENATED MODULE: ./src/placeholder.ts
  80. function createPlaceholderElement(type, width, height, top, left, color) {
  81. var placeholder = document.createElement('div');
  82. placeholder.classList.add("spacing-js-".concat(type, "-placeholder"));
  83. placeholder.style.border = "2px solid ".concat(color);
  84. placeholder.style.position = 'fixed';
  85. placeholder.style.background = 'none';
  86. placeholder.style.borderRadius = '2px';
  87. placeholder.style.padding = '0';
  88. placeholder.style.margin = '0';
  89. placeholder.style.width = "".concat(width - 2, "px");
  90. placeholder.style.height = "".concat(height - 2, "px");
  91. placeholder.style.top = "".concat(top - 1, "px");
  92. placeholder.style.left = "".concat(left - 1, "px");
  93. placeholder.style.pointerEvents = 'none';
  94. placeholder.style.zIndex = '9999';
  95. placeholder.style.boxSizing = 'content-box';
  96. document.body.appendChild(placeholder);
  97. }
  98. function clearPlaceholderElement(type) {
  99. var _document$querySelect;
  100.  
  101. (_document$querySelect = document.querySelector(".spacing-js-".concat(type, "-placeholder"))) === null || _document$querySelect === void 0 ? void 0 : _document$querySelect.remove();
  102. }
  103. ;// CONCATENATED MODULE: ./src/marker.ts
  104. function createLine(width, height, top, left, text) {
  105. var border = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 'none';
  106. var marker = document.createElement('span');
  107. marker.style.backgroundColor = 'red';
  108. marker.style.position = 'fixed';
  109. marker.classList.add("spacing-js-marker");
  110. marker.style.width = "".concat(width, "px");
  111. marker.style.height = "".concat(height, "px");
  112.  
  113. if (border === 'x') {
  114. marker.style.borderLeft = '1px solid rgba(255, 255, 255, .8)';
  115. marker.style.borderRight = '1px solid rgba(255, 255, 255, .8)';
  116. }
  117.  
  118. if (border === 'y') {
  119. marker.style.borderTop = '1px solid rgba(255, 255, 255, .8)';
  120. marker.style.borderBottom = '1px solid rgba(255, 255, 255, .8)';
  121. }
  122.  
  123. marker.style.pointerEvents = 'none';
  124. marker.style.top = "".concat(top, "px");
  125. marker.style.left = "".concat(left, "px");
  126. marker.style.zIndex = '9998';
  127. marker.style.boxSizing = 'content-box';
  128. var value = document.createElement('span');
  129. value.classList.add("spacing-js-value");
  130. value.style.backgroundColor = 'red';
  131. value.style.color = 'white';
  132. value.style.fontSize = '10px';
  133. value.style.display = 'inline-block';
  134. value.style.fontFamily = 'Helvetica, sans-serif';
  135. value.style.fontWeight = 'bold';
  136. value.style.borderRadius = '20px';
  137. value.style.position = 'fixed';
  138. value.style.width = '42px';
  139. value.style.lineHeight = '15px';
  140. value.style.height = '16px';
  141. value.style.textAlign = 'center';
  142. value.style.zIndex = '10000';
  143. value.style.pointerEvents = 'none';
  144. value.innerText = text;
  145. value.style.boxSizing = 'content-box';
  146.  
  147. if (border === 'x') {
  148. // Prevent the badge moved outside the screen
  149. var topOffset = top + height / 2 - 7;
  150.  
  151. if (topOffset > document.documentElement.clientHeight - 20) {
  152. topOffset = document.documentElement.clientHeight - 20;
  153. }
  154.  
  155. if (topOffset < 0) {
  156. topOffset = 6;
  157. }
  158.  
  159. value.style.top = "".concat(topOffset, "px");
  160. value.style.left = "".concat(left + 6, "px");
  161. } else if (border === 'y') {
  162. // Prevent the badge moved outside the screen
  163. var leftOffset = left + width / 2 - 20;
  164.  
  165. if (leftOffset > document.documentElement.clientWidth - 48) {
  166. leftOffset = document.documentElement.clientWidth - 48;
  167. }
  168.  
  169. if (leftOffset < 0) {
  170. leftOffset = 6;
  171. }
  172.  
  173. value.style.top = "".concat(top + 6, "px");
  174. value.style.left = "".concat(leftOffset, "px");
  175. }
  176.  
  177. document.body.appendChild(marker);
  178. document.body.appendChild(value);
  179. }
  180.  
  181. function placeMark(rect1, rect2, direction, value) {
  182. var edgeToEdge = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
  183.  
  184. if (direction === 'top') {
  185. var width = 1;
  186. var height = Math.abs(rect1.top - rect2.top);
  187. var left = Math.floor((Math.min(rect1.right, rect2.right) + Math.max(rect1.left, rect2.left)) / 2);
  188. var top = Math.min(rect1.top, rect2.top);
  189.  
  190. if (edgeToEdge) {
  191. if (rect1.top < rect2.top) {
  192. return;
  193. } // If not colliding
  194.  
  195.  
  196. if (rect1.right < rect2.left || rect1.left > rect2.right) {
  197. return;
  198. }
  199.  
  200. height = Math.abs(rect2.bottom - rect1.top);
  201. top = Math.min(rect2.bottom, rect1.top);
  202. }
  203.  
  204. createLine(width, height, top, left, value, 'x');
  205. } else if (direction === 'left') {
  206. var _width = Math.abs(rect1.left - rect2.left);
  207.  
  208. var _height = 1;
  209.  
  210. var _top = Math.floor((Math.min(rect1.bottom, rect2.bottom) + Math.max(rect1.top, rect2.top)) / 2);
  211.  
  212. var _left = Math.min(rect1.left, rect2.left);
  213.  
  214. if (edgeToEdge) {
  215. if (rect1.left < rect2.left) {
  216. return;
  217. } // If not overlapping
  218.  
  219.  
  220. if (rect1.bottom < rect2.top || rect1.top > rect2.bottom) {
  221. return;
  222. }
  223.  
  224. _width = Math.abs(rect1.left - rect2.right);
  225. _left = Math.min(rect2.right, rect1.left);
  226. }
  227.  
  228. createLine(_width, _height, _top, _left, value, 'y');
  229. } else if (direction === 'right') {
  230. var _width2 = Math.abs(rect1.right - rect2.right);
  231.  
  232. var _height2 = 1;
  233.  
  234. var _top2 = Math.floor((Math.min(rect1.bottom, rect2.bottom) + Math.max(rect1.top, rect2.top)) / 2);
  235.  
  236. var _left2 = Math.min(rect1.right, rect2.right);
  237.  
  238. if (edgeToEdge) {
  239. if (rect1.left > rect2.right) {
  240. return;
  241. } // If not overlapping
  242.  
  243.  
  244. if (rect1.bottom < rect2.top || rect1.top > rect2.bottom) {
  245. return;
  246. }
  247.  
  248. _width2 = Math.abs(rect1.right - rect2.left);
  249. }
  250.  
  251. createLine(_width2, _height2, _top2, _left2, value, 'y');
  252. } else if (direction === 'bottom') {
  253. var _width3 = 1;
  254.  
  255. var _height3 = Math.abs(rect1.bottom - rect2.bottom);
  256.  
  257. var _top3 = Math.min(rect1.bottom, rect2.bottom);
  258.  
  259. var _left3 = Math.floor((Math.min(rect1.right, rect2.right) + Math.max(rect1.left, rect2.left)) / 2);
  260.  
  261. if (edgeToEdge) {
  262. if (rect2.bottom < rect1.top) {
  263. return;
  264. } // If not overlapping
  265.  
  266.  
  267. if (rect1.right < rect2.left || rect1.left > rect2.right) {
  268. return;
  269. }
  270.  
  271. _height3 = Math.abs(rect1.bottom - rect2.top);
  272. }
  273.  
  274. createLine(_width3, _height3, _top3, _left3, value, 'x');
  275. }
  276. }
  277. function removeMarks() {
  278. document.querySelectorAll('.spacing-js-marker').forEach(function (element) {
  279. element.remove();
  280. });
  281. document.querySelectorAll('.spacing-js-value').forEach(function (element) {
  282. element.remove();
  283. });
  284. }
  285. ;// CONCATENATED MODULE: ./src/spacing.ts
  286.  
  287.  
  288.  
  289. var active = false;
  290. var selectedElement;
  291. var targetElement;
  292. var originalBodyOverflow;
  293. var Spacing = {
  294. start: function start() {
  295. if (!document.body) {
  296. console.warn("Unable to initialise, document.body does not exist.");
  297. return;
  298. }
  299.  
  300. window.addEventListener('keydown', function (e) {
  301. if (e.key === 'Alt' && !active) {
  302. e.preventDefault();
  303. active = true;
  304. setSelectedElement();
  305. preventPageScroll(true);
  306. }
  307. });
  308. window.addEventListener('keyup', function (e) {
  309. active = false;
  310. clearPlaceholderElement('selected');
  311. clearPlaceholderElement('target');
  312. selectedElement = null;
  313. targetElement = null;
  314. removeMarks();
  315. preventPageScroll(false);
  316. });
  317. window.addEventListener('mousemove', function (e) {
  318. setTargetElement().then(function () {
  319. if (selectedElement != null && targetElement != null) {
  320. // Do the calculation
  321. var selectedElementRect = selectedElement.getBoundingClientRect();
  322. var targetElementRect = targetElement.getBoundingClientRect();
  323. var selected = new Rect(selectedElementRect);
  324. var target = new Rect(targetElementRect);
  325. removeMarks();
  326. var top, bottom, left, right, outside;
  327.  
  328. if (selected.containing(target) || selected.inside(target) || selected.colliding(target)) {
  329. console.log("containing || inside || colliding");
  330. top = Math.round(Math.abs(selectedElementRect.top - targetElementRect.top));
  331. bottom = Math.round(Math.abs(selectedElementRect.bottom - targetElementRect.bottom));
  332. left = Math.round(Math.abs(selectedElementRect.left - targetElementRect.left));
  333. right = Math.round(Math.abs(selectedElementRect.right - targetElementRect.right));
  334. outside = false;
  335. } else {
  336. console.log("outside");
  337. top = Math.round(Math.abs(selectedElementRect.top - targetElementRect.bottom));
  338. bottom = Math.round(Math.abs(selectedElementRect.bottom - targetElementRect.top));
  339. left = Math.round(Math.abs(selectedElementRect.left - targetElementRect.right));
  340. right = Math.round(Math.abs(selectedElementRect.right - targetElementRect.left));
  341. outside = true;
  342. }
  343.  
  344. placeMark(selected, target, 'top', "".concat(top, "px"), outside);
  345. placeMark(selected, target, 'bottom', "".concat(bottom, "px"), outside);
  346. placeMark(selected, target, 'left', "".concat(left, "px"), outside);
  347. placeMark(selected, target, 'right', "".concat(right, "px"), outside);
  348. }
  349. });
  350. });
  351. }
  352. };
  353.  
  354. function setSelectedElement() {
  355. var elements = document.querySelectorAll(':hover');
  356. var el = elements[elements.length - 1];
  357.  
  358. if (el !== selectedElement) {
  359. selectedElement = el;
  360. clearPlaceholderElement('selected');
  361. var rect = selectedElement.getBoundingClientRect();
  362. createPlaceholderElement('selected', rect.width, rect.height, rect.top, rect.left, "red");
  363. }
  364. }
  365.  
  366. function setTargetElement() {
  367. return new Promise(function (resolve, reject) {
  368. var elements = document.querySelectorAll(':hover');
  369. var el = elements[elements.length - 1];
  370.  
  371. if (active && el !== selectedElement && el !== targetElement) {
  372. targetElement = el;
  373. clearPlaceholderElement('target');
  374. var rect = targetElement.getBoundingClientRect();
  375. createPlaceholderElement('target', rect.width, rect.height, rect.top, rect.left, 'blue');
  376. resolve();
  377. }
  378. });
  379. }
  380.  
  381. function preventPageScroll(active) {
  382. if (active) {
  383. window.addEventListener('DOMMouseScroll', scrollingPreventDefault, false);
  384. window.addEventListener('wheel', scrollingPreventDefault, {
  385. passive: false
  386. });
  387. window.addEventListener('mousewheel', scrollingPreventDefault, {
  388. passive: false
  389. });
  390. } else {
  391. window.removeEventListener('DOMMouseScroll', scrollingPreventDefault);
  392. window.removeEventListener('wheel', scrollingPreventDefault);
  393. window.removeEventListener('mousewheel', scrollingPreventDefault);
  394. }
  395. }
  396.  
  397. function scrollingPreventDefault(e) {
  398. e.preventDefault();
  399. }
  400.  
  401. /* harmony default export */ const spacing = (Spacing);
  402. ;// CONCATENATED MODULE: ./src/index.ts
  403. // Simple, Start.
  404.  
  405. spacing.start();
  406. window.Spacing = __webpack_exports__;
  407. /******/ })()
  408. ;