Element Fullscreen

Fullscreen any element on a webpage

  1. // ==UserScript==
  2. // @name Element Fullscreen
  3. // @namespace http://shitchell.com/
  4. // @include *
  5. // @description Fullscreen any element on a webpage
  6. // @author Shaun Mitchell <shaun@shitchell.com>
  7. // @license wtfpl
  8. // @grant GM_addStyle
  9. // @version 0.3
  10. // ==/UserScript==
  11.  
  12. // Send stuff to the console
  13. var DEBUG = false;
  14.  
  15. // Key combination to activate element selection (default is Alt-f)
  16. var toggleElementSelectionKey = "F";
  17. var toggleElementSelectionAlt = false;
  18. var toggleElementSelectionCtrl = true;
  19.  
  20. // Styles and css selectors
  21. var focusedStyle = `box-shadow: 0 3px 6px rgba(0,0,0,0.16),
  22. 0 3px 6px rgba(0,0,0,0.23),
  23. 0 3px 6px rgba(255,255,255,0.16),
  24. 0 3px 6px rgba(255,255,255,0.23) !important;`;
  25. var focusedSelector = "element-f";
  26. var fullScreenStyle = "padding: 1em !important;";
  27. var fullScreenSelector = "element-f-fullscreen";
  28.  
  29. // Element tracking
  30. var focusedElement = null;
  31.  
  32. // Start off not running until the defined keypress
  33. var running = false;
  34.  
  35. function debug()
  36. {
  37. if (DEBUG)
  38. {
  39. let args = Array.from(arguments);
  40. args.unshift("[Element-F]");
  41. console.log.apply(null, args);
  42. }
  43. }
  44.  
  45. /*
  46. * Returns a boolean that describes whether or not an element is fullscreened
  47. */
  48. function isFullScreen()
  49. {
  50. return document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen;
  51. }
  52.  
  53. // Get the element directly under the mouse
  54. // https://stackoverflow.com/a/24540416/794241
  55. function getInnermostHovered()
  56. {
  57. return [].slice.call(document.querySelectorAll(':hover')).pop();
  58. }
  59.  
  60. /*
  61. * Removes any styling from any previously focused elements
  62. */
  63. function resetFocused()
  64. {
  65. debug("resetting any focused elements");
  66.  
  67. // Remove the focused class from any elements that have it
  68. document.querySelectorAll(`.${focusedSelector}`).forEach(function(el)
  69. {
  70. el.classList.remove(focusedSelector);
  71. debug("CLEARED: ", el);
  72. });
  73.  
  74. // Unset the focused element
  75. focusedElement = null;
  76. }
  77.  
  78. /*
  79. * Sets the currently hovered element as the focused element and
  80. * unsets any previously focused elements
  81. */
  82. function focusElement(el)
  83. {
  84. // Make sure we're running and the element isn't already focused
  85. if (!running || el === focusedElement)
  86. {
  87. return false;
  88. }
  89.  
  90. // Clear any previously focused elements
  91. resetFocused();
  92.  
  93. // Set the focus to this element
  94. focusedElement = el;
  95. debug("FOCUS: ", el);
  96.  
  97. // Add the hover class
  98. el.classList.add(focusedSelector);
  99. }
  100.  
  101. /*
  102. * Grabs the element under the cursor and sets it to focused
  103. */
  104. function setFocusedElement()
  105. {
  106. if (!running)
  107. {
  108. return false;
  109. }
  110.  
  111. let hoveredElement = getInnermostHovered();
  112. if (hoveredElement !== undefined)
  113. {
  114. focusElement(hoveredElement);
  115. }
  116. }
  117.  
  118. /*
  119. * Accepts an event from a listener and then fullscreens the target element
  120. */
  121. function fullScreenElement(ev)
  122. {
  123. if (!running)
  124. {
  125. return false;
  126. }
  127.  
  128. // Prevent whatever the event would have triggered (like following a link)
  129. ev.stopPropagation();
  130. ev.preventDefault();
  131.  
  132. if (ev.target !== null)
  133. {
  134. let req = ev.target.requestFullScreen || ev.target.webkitRequestFullScreen || ev.target.mozRequestFullScreen;
  135. if (req !== undefined)
  136. {
  137. // Fullscreen the target element
  138. req.call(ev.target);
  139.  
  140. // Add fullscreen class
  141. ev.target.classList.add(fullScreenSelector);
  142.  
  143. // Remove the fullscreen class after we're no longer fullscreened
  144. ev.target.addEventListener('fullscreenchange', function exitFullScreen() {
  145. if (!isFullScreen())
  146. {
  147. ev.target.classList.remove(fullScreenSelector);
  148. ev.target.removeEventListener('fullscreenchange', exitFullScreen);
  149. }
  150. });
  151.  
  152. // Make sure the target element has a background set
  153. ensureBackground(ev.target);
  154.  
  155. // Stop running
  156. running = false;
  157.  
  158. // Unset the target element as focused
  159. resetFocused();
  160. }
  161. }
  162. }
  163.  
  164. /*
  165. * Returns true if the specified key combination was pressed
  166. * to initiate element selection.
  167. */
  168. function validateKeyPress(ev)
  169. {
  170. if (ev.altKey != toggleElementSelectionAlt)
  171. {
  172. return false;
  173. }
  174. if (ev.ctrlKey != toggleElementSelectionCtrl)
  175. {
  176. return false;
  177. }
  178. if (ev.key != toggleElementSelectionKey)
  179. {
  180. return false;
  181. }
  182. debug("keypress triggered");
  183. return true;
  184. }
  185.  
  186. /*
  187. * Accepts a keypress event and then toggles running (ie, element selection mode)
  188. */
  189. function toggleRunning(ev)
  190. {
  191. if (validateKeyPress(ev)) {
  192. // Prevent whatever the keypress would have triggered
  193. ev.stopPropagation();
  194. ev.preventDefault();
  195.  
  196. running = !running;
  197. debug("toggled running =>", running);
  198.  
  199. // Remove any focused elements if not running
  200. if (!running)
  201. {
  202. resetFocused();
  203. }
  204. }
  205. }
  206.  
  207. /*
  208. * Some elements are not set with a background color, defaulting to black in
  209. * fullscreen mode, which sometimes makes the text hard to read. This method
  210. * will check to see if an element lacks a background color and, if it does not,
  211. * temporarily gives it a black or white background based on its text color.
  212. */
  213. function ensureBackground(el)
  214. {
  215. debug("testing background for", el);
  216.  
  217. // First check to see that there isn't a background already
  218. let cS = getComputedStyle(el);
  219. let bgColor = cS.backgroundColor;
  220. if (bgColor == "rgba(0, 0, 0, 0)") // no background color is set
  221. {
  222. let textColor = getComputedStyle(el).color;
  223. textColor = textColor.substring(textColor.indexOf('(') +1, textColor.length -1).split(', ');
  224. textColor = {
  225. 'r': textColor[0],
  226. 'g': textColor[1],
  227. 'b': textColor[2]
  228. };
  229. bgColor = yiq(textColor.r, textColor.g, textColor.b);
  230.  
  231. // Set the background back to nothing after we exit fullscreen
  232. el.addEventListener('fullscreenchange', function removeBackground()
  233. {
  234. debug("potentially removing temporary background from", el);
  235. // Only run if the screen changed and exited fullscreen mode
  236. if (!isFullScreen())
  237. {
  238. debug("removing temporary background from", el);
  239. el.style.backgroundColor = null;
  240. el.removeEventListener('fullscreenchange', removeBackground);
  241. }
  242. });
  243. el.style.backgroundColor = bgColor;
  244. debug("YIQ: Got bg color", bgColor);
  245. }
  246. return bgColor;
  247. }
  248.  
  249. /*
  250. * Determines whether black or white is more appropriate for
  251. * a given color using YIQ computation
  252. */
  253. function yiq(r, g, b)
  254. {
  255. let color = Math.round(((parseInt(r) * 299) +
  256. (parseInt(g) * 587) +
  257. (parseInt(b) * 114)) / 1000);
  258. return (color > 125) ? 'black' : 'white';
  259. }
  260.  
  261. (function()
  262. {
  263. 'use strict';
  264.  
  265. // Set the style for the actively hovered element
  266. GM_addStyle(`.${focusedSelector} {
  267. cursor: crosshair !important;
  268. ${focusedStyle};
  269. }`);
  270.  
  271. // Set the style for the fullscreened element
  272. GM_addStyle(`.${fullScreenSelector} {
  273. overflow: auto !important;
  274. ${fullScreenStyle};
  275. }`);
  276.  
  277. // Toggle whether or not we're looking for elements based on the defined keypress
  278. document.body.addEventListener('keydown', toggleRunning);
  279.  
  280. // Set the element under the cursor to the focused element (only if running)
  281. document.body.addEventListener('mousemove', setFocusedElement);
  282.  
  283. // Listen for a click and fullscreen that element (only if running)
  284. document.body.addEventListener('click', fullScreenElement, true);
  285. })();