Hover Zoom Revamped

Basic userscript implementation of the Imagus Hoverzoom extension

  1. // ==UserScript==
  2. // @name Hover Zoom Revamped
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.2
  5. // @description Basic userscript implementation of the Imagus Hoverzoom extension
  6. // @author TetteDev
  7. // @match *://*/*
  8. // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
  9. // @license MIT
  10. // @run-at document-start
  11. // @noframes
  12. // ==/UserScript==
  13.  
  14. const HoverImageId = `hover_image_${makeid(6)}`;
  15. const HoverAttribute = "data-hoverable";
  16. const NodeIsImage = (node) => { return node.tagName === "IMG" || node.nodeName === "IMG"; };
  17.  
  18. let mouseX = 0;
  19. let mouseClientX = 0;
  20. let mouseY = 0;
  21. let mouseClientY = 0;
  22. let renderAtMousePosition = false;
  23. if (renderAtMousePosition) {
  24. const fnUpdateMousePosition = (e) => {
  25. mouseX = e.pageX;
  26. mouseClientX = e.clientX;
  27. mouseY = e.pageY;
  28. mouseClientY = e.clientY;
  29. };
  30. document.addEventListener('mousemove', fnUpdateMousePosition, false);
  31. };
  32.  
  33. function containsImageDimensions(url) {
  34. const dimensionRegex = /(\d{2,4})x(\d{2,4})/;
  35. const match = url.match(dimensionRegex);
  36.  
  37. if (match) {
  38. const width = parseInt(match[1], 10);
  39. const height = parseInt(match[2], 10);
  40.  
  41. if (width > 0 && height > 0 && width <= 10000 && height <= 10000) {
  42. return {
  43. width: width,
  44. height: height
  45. };
  46. }
  47. }
  48. return null;
  49. }
  50. function isLazyLoaded(imgElement) {
  51. if (!imgElement || imgElement.tagName.toLowerCase() !== 'img') {
  52. return false;
  53. }
  54.  
  55. if (imgElement.hasAttribute('loading') && imgElement.getAttribute('loading') === 'lazy') {
  56. return true;
  57. }
  58.  
  59. const lazyAttributes = ['data-src', 'data-lazy-src', 'data-srcset'];
  60. if (lazyAttributes.some((attr) => imgElement.hasAttribute(attr))) {
  61. return true;
  62. }
  63.  
  64. const possibleIndicatorsClassnameLazy = ["lazy", "lazyloaded"];
  65. if (possibleIndicatorsClassnameLazy.some((lazy) => imgElement.className.includes(lazy))) {
  66. return true;
  67. }
  68.  
  69. // check if src might contain 'lazy' or 'lazyloaded'
  70.  
  71. return false;
  72. }
  73. function makeid(length, allowedCharacters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz') {
  74. let result = '';
  75. const characters = allowedCharacters || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
  76. const charactersLength = characters.length;
  77. let counter = 0;
  78. while (counter < length) {
  79. result += characters.charAt(Math.floor(Math.random() * charactersLength));
  80. counter += 1;
  81. }
  82. return result;
  83. }
  84.  
  85. (function() {
  86. 'use strict';
  87.  
  88. const fnTryQuerySelector = (node, selector, all = false) => {
  89. if (!node) return [];
  90. if (!selector) return [];
  91. try {
  92. return all ? node.querySelectorAll(selector) : node.querySelector(selector);
  93. } catch (err) { return []; }
  94. };
  95. const fnTryHasAttribute = (node, attributeName) => {
  96. if(typeof node === 'object' && node !== null && 'getAttribute' in node && node.hasAttribute(attributeName)) return true;
  97. else return false;
  98. };
  99.  
  100. window.addEventListener('load', function () {
  101. document.querySelectorAll("img").forEach((node) => {
  102. const isImageOrContainsImage = NodeIsImage(node) || (fnTryQuerySelector(node, "img", true).length > 0);
  103. if (isImageOrContainsImage) {
  104. if (fnTryHasAttribute(node, HoverAttribute)) return;
  105.  
  106. node.setAttribute(HoverAttribute, "true");
  107. node.addEventListener("mouseenter", OnElementMouseEnter); node.addEventListener("mouseleave", OnElementMouseLeave);
  108. }
  109. });
  110. new MutationObserver((mutationRecords, observer) => {
  111. mutationRecords.forEach((mutation) => {
  112. const addedNodes = mutation.addedNodes;
  113. if (addedNodes.length == 0) return;
  114.  
  115. addedNodes.forEach((node) => {
  116. if (node.id === HoverImageId) return;
  117. if (fnTryHasAttribute(node, HoverAttribute)) return;
  118.  
  119. const isImageOrContainsImage = NodeIsImage(node) || (fnTryQuerySelector(node, "img", true).length > 0);
  120. // TODO: if NodeIsImage(node) returned false but isImageOrContainsImage is still true
  121. // determine if its worth making it hoverable by checking the child "img" tags in that
  122. // element and confirming they meet some kind of requirements, such as
  123. // bigger than some size, child images count is only 1 etc etc
  124.  
  125. if (isImageOrContainsImage) {
  126. node.setAttribute(HoverAttribute, "true");
  127. node.addEventListener("mouseenter", OnElementMouseEnter); node.addEventListener("mouseleave", OnElementMouseLeave);
  128. }
  129. });
  130. });
  131. }).observe(document, { attributes: false, childList: true, characterData: false, subtree: true });
  132. });
  133.  
  134. function OnElementMouseEnter(event) {
  135. OnElementMouseLeave(null);
  136.  
  137. const targetImage = NodeIsImage(event.currentTarget) ? event.currentTarget : event.currentTarget.querySelector("img");
  138. let targetImageSource = "";
  139. if (isLazyLoaded(targetImage)) {
  140. let dataSetUrls = targetImage.hasAttribute("data-srcset") ? targetImage.getAttribute("data-srcset").split(',').map((url) => url.trim().split(' ')[0]) : [];
  141. if (dataSetUrls) {
  142. targetImageSource = dataSetUrls.pop();
  143. }
  144. else {
  145. targetImageSource = targetImage.getAttribute("data-src");
  146. }
  147. }
  148. else {
  149. targetImageSource = targetImage.src;
  150. }
  151.  
  152. if (!targetImageSource) {
  153. targetImageSource = targetImage.getAttribute("data-src") || targetImage.src || null;
  154.  
  155. if (!targetImageSource) {
  156. event.currentTarget.removeEventListener("mouseenter", OnElementMouseEnter);
  157. event.currentTarget.removeEventListener("mouseleave", OnElementMouseLeave);
  158. return;
  159. }
  160. }
  161. let isBase64 = targetImageSource.includes("base64");
  162.  
  163. let width = "auto";
  164. let height = "auto";
  165.  
  166. let urlDerivedImageDimensions = isBase64 ? null : containsImageDimensions(targetImageSource);
  167. if (urlDerivedImageDimensions) {
  168. width = `${urlDerivedImageDimensions.width}px`;
  169. height = `${urlDerivedImageDimensions.height}px`;
  170. } else {
  171. let hasWidthAttribute = targetImage.hasAttribute("width");
  172. if (hasWidthAttribute) width = `${targetImage.getAttribute("width")}px`;
  173.  
  174. let hasHeightAttribute = targetImage.hasAttribute("height");
  175. if (hasHeightAttribute) height = `${targetImage.getAttribute("height")}px`;
  176. }
  177.  
  178. // Default: render it at the center of the page
  179. let renderPositionX = "50%";
  180. let renderPositionY = "50%";
  181. if (renderAtMousePosition) {
  182. // Render at mouse
  183. renderPositionX = `${(mouseX)}px`;
  184. renderPositionY = `${(mouseY)}px`;
  185.  
  186. // Render at mouse with some offset
  187. /*
  188. const imageWidth = parseInt(width);
  189. const imageHeight = parseInt(height);
  190. renderPositionX = (mouseClientX + (imageWidth / 2));
  191. if (renderPositionX + imageWidth > window.innerWidth) {
  192. renderPositionX = window.innerWidth - imageWidth;
  193. }
  194. renderPositionY = (mouseClientY + (imageHeight / 2));
  195. if (renderPositionY + imageHeight > window.innerHeight) {
  196. renderPositionY = window.innerHeight - imageHeight;
  197. }
  198. renderPositionX = `${renderPositionY}px`;
  199. renderPositionY = `${renderPositionY}px`;
  200. */
  201.  
  202.  
  203. // Render at mouse but use percentages relative to the page view
  204. /*
  205. const xOffsetPercentage = 20;
  206. const yOffsetPercentage = 25;
  207. const xPositionPercentage = ((mouseX/window.innerWidth)*100) + xOffsetPercentage;
  208. const yPositionPercentage = ((mouseY/window.innerHeight)*100) + yOffsetPercentage;
  209.  
  210. renderPositionX = `${xPositionPercentage}%`;
  211. renderPositionY = `${yPositionPercentage}%`;
  212. */
  213. }
  214.  
  215. const hover = document.createElement("img");
  216. hover.src = targetImageSource;
  217. hover.id = HoverImageId;
  218. hover.style.cssText = `
  219. margin: 0 auto;
  220. position: ${(renderAtMousePosition ? 'absolute' : 'fixed')};
  221. left: ${renderPositionX} !important;
  222. top: ${renderPositionY} !important;
  223. transform: translate(-50%, -50%);
  224. width: ${width} !important;
  225. height: ${height} !important;
  226. z-index: 9999;
  227. border: 1px dashed red;`;
  228.  
  229. document.body.appendChild(hover);
  230. }
  231.  
  232. function OnElementMouseLeave(event) {
  233. let hover = document.querySelector(`#${HoverImageId}`);
  234. if (hover) hover.remove();
  235. }
  236. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址