Hover Zoom Minus --

image popup: zoom, pan, scroll, pin, scale

  1. // ==UserScript==
  2. // @name Hover Zoom Minus --
  3. // @namespace Hover Zoom Minus --
  4. // @version 1.0.9.10
  5. // @description image popup: zoom, pan, scroll, pin, scale
  6. // @author Ein, Copilot AI
  7. // @match *://*/*
  8. // @license MIT
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. // 01. to use hover over the image(container) to view a popup of the target image
  13. // 02. to zoom in/out use wheel up/down.
  14. // 03. click left mouse to lock popup this will make it move along with the mouse, click again to release (indicated by green border)
  15. // 04. while being locked"Y" the wheel up/down will act as scroll up/down
  16. // 05. double click will lock it on screen preventing it from being hidden
  17. // 06. hover below the image and click blue bar this will make a 3rd mode for wheel bottom, which will scroll next/previous image under an album
  18. // 07. while locked at screen (indicated by red outline) a single click with the blurred background will unblur it, only one popup per time, so the locked popup will prevent other popup to spawn
  19. // 08. double clicking on blurred background will de-spawn popup
  20. // 09. click on the corner to toggle scaling (blue) will retain current aspect ratio, double clicking will turn to 2nd mode for scaling (magenta) will not retain current aspect ratio
  21. // 10. return to original aspect ration via clicking on sides, if it's in an album to reset to aspect ratio on bottom side use double click
  22. // 11. you can now crop and save the image, just double click the top to initiate.
  23. // 12. to turn on/off hover at the bottom of the page
  24.  
  25. (function() {
  26. 'use strict';
  27.  
  28. // Configuration ----------------------------------------------------------
  29.  
  30. // Define regexp of web page you want HoverZoomMinus to run with,
  31. // 1st array value - default status at start of page: '1' for on, '0' for off
  32. // 2nd array value - spawn position for popup: 'center' for center of screen, '' for cursor position
  33. // 3rd array value - allowed interval for spawning popup; i.e. when exited on popup but immediately touches an img container thus making it "blink spawn blink spawn", experiment it with the right number
  34.  
  35. const siteConfig = {
  36. 'reddit.com': [1, 'center', '100'],
  37. '9gag.com': [1, 'center', '0'],
  38. 'feedly.com': [1, 'center', '200'],
  39. '4chan.org': [1, '', '400'],
  40. 'deviantart.com': [0, 'center', '300'],
  41. 'home': [1, 'center', '0'] /* for testing */
  42. };
  43.  
  44. // image container [hover box where popup triggers]
  45. const imgContainers = `
  46. /* ------- reddit */ ._3BxRNDoASi9FbGX01ewiLg, ._3Oa0THmZ3f5iZXAQ0hBJ0k > div, ._35oEP5zLnhKEbj5BlkTBUA, ._1ti9kvv_PMZEF2phzAjsGW > div,
  47. /* ------- reddit */ ._28TEYBuEdOuE3kN6UyoKMa div, ._3Oa0THmZ3f5iZXAQ0hBJ0k.WjuR4W-BBrvdtABBeKUMx div, ._3m20hIKOhTTeMgPnfMbVNN, zoomable-img.fixed,
  48. /* --------- 9gag */ .post-container .post-view,
  49. /* ------- feedly */ .PinableImageContainer, .entryBody,
  50. /* -------- 4chan */ div.post div.file a,
  51. /* --- deviantart */ ._3_LJY, ._2e1g3, ._2SlAD, ._1R2x6
  52. `;
  53. // target img
  54. const imgElements = `
  55. /* ------- reddit */ ._2_tDEnGMLxpM6uOa2kaDB3, ._1dwExqTGJH2jnA-MYGkEL-, ._2_tDEnGMLxpM6uOa2kaDB3._1XWObl-3b9tPy64oaG6fax, zoomable-img.fixed img,
  56. /* --------- 9gag */ .post-container .post-view > picture > img,
  57. /* ------- feedly */ .pinable, .entryBody img,
  58. /* -------- 4chan */ div.post div.file img:nth-child(1), ._3Oa0THmZ3f5iZXAQ0hBJ0k.WjuR4W-BBrvdtABBeKUMx img, div.post div.file .fileThumb img,
  59. /* --- deviantart */ ._3_LJY img, ._2e1g3 img, ._2SlAD img, ._1R2x6 img
  60. `;
  61. // excluded element
  62. const nopeElements = `
  63. /* ------- reddit */ ._2ED-O3JtIcOqp8iIL1G5cg
  64. `;
  65. // AlbumSelector take note that it will only load image that are already in the DOM tree
  66. // example reddit will not include all until you press navigator buttons so most of the time this will only load a few image
  67. // unless you update it via interacting with navigator button thus updating the DOM tree (added new function for navigation it can now update)
  68. let albumSelector = [
  69. /* ---reddit */ { imgElement: '._1dwExqTGJH2jnA-MYGkEL-', albumElements: '._1apobczT0TzIKMWpza0OhL' },
  70. /* ---feedly */ { imgElement: '.entryBody > div > img', albumElements: '.entryBody > div' },
  71. /* ---feedly */ { imgElement: 'div[id^="Article-"] > div > span > img', albumElements: 'div[id^="Article-"]' },
  72. ];
  73.  
  74. // specialElements were if targeted, will call it's paired function
  75. // for convenience an element className "specialElement" will be remove during mouseout or function hidepopup() is called you can use that with specialElements function for temporary elements
  76. const specialElements = [
  77. /* --- 4chan */ { selector: 'div.post div.file .fileThumb img', func: SP1 },
  78. /* -- reddit */ { selector: '._1dwExqTGJH2jnA-MYGkEL-', func: SP2 },
  79. /* -- reddit */ { selector: '._2_tDEnGMLxpM6uOa2kaDB3', func: SP3 },
  80. /* -- reddit */ { selector: '._2_tDEnGMLxpM6uOa2kaDB3', func: SPunblurSpoiler },
  81. ];
  82. // special function triggered when clicking LeftBar/ wheel down in album mode
  83. const specialLeftBar = [
  84. /* -- reddit */ { selector: '._1dwExqTGJH2jnA-MYGkEL-', func: SPL }
  85. ];
  86. // special function triggered when clicking RightBar/ wheel up in album mode
  87. const specialRightBar = [
  88. /* -- reddit */ { selector: '._1dwExqTGJH2jnA-MYGkEL-', func: SPR }
  89. ];
  90. //-------------------------------------------------------------------------
  91. // Special Funtions -------------------------------------------------------
  92.  
  93.  
  94. // 4chan: replaces thumbnail so popup will use the larger version, or if its animated replace it with that
  95. function SP1(imageElement) {
  96. const parentElement = imageElement.parentElement;
  97. const href = parentElement.getAttribute('href');
  98. if (parentElement.tagName === 'a') {
  99. if (href.endsWith('.webm')) {
  100. const videoElement = document.createElement('video');
  101. videoElement.src = href;
  102. videoElement.controls = true;
  103. parentElement.replaceChild(videoElement, imageElement);
  104. } else {
  105. imageElement.setAttribute('src', href);
  106. }
  107. } else {
  108. imageElement.setAttribute('src', href);
  109. }
  110. }
  111.  
  112. // reddit: added the label/description of image along with the popup
  113. function SP2(imageElement) {
  114. if (enableP === 0) {
  115. return;
  116. }
  117. let closestElement = imageElement.closest('.m3aNC6yp8RrNM_-a0rrfa, .kcerW9lbT-se3SXd-wp2i');
  118. if (!closestElement) {
  119. return;
  120. }
  121. let descendantElement = closestElement.querySelector('._15nNdGlBIgryHV04IfAfpA');
  122. if (descendantElement) {
  123. document.querySelectorAll('.specialElement').forEach(e => e.remove());
  124. let title = descendantElement.getAttribute('title');
  125. const specialContent = document.createElement('div');
  126. specialContent.className = 'specialElement';
  127. specialContent.style.cssText = 'position:fixed; bottom:80px; left:50%; transform:translateX(-50%); width:100vw; text-align:center; font-size:40px; color:white; text-shadow:0 0 10px rgba(255,255,255,0.7); z-index:99999;';
  128. specialContent.textContent = title;
  129. document.body.appendChild(specialContent);
  130.  
  131. var specialBackdrop = document.createElement('div');
  132. specialBackdrop.className = 'specialElement';
  133. specialBackdrop.style.cssText = 'position:fixed; bottom:60px; left:0; width:100vw; height:80px; background:rgba(0,0,0,0.2); backdrop-filter:blur(5px); z-index:99998;';
  134. document.body.appendChild(specialBackdrop);
  135.  
  136. } else {
  137. return;
  138. }
  139. }
  140. function SP3(imageElement) {
  141. if (enableP === 0) {
  142. return;
  143. }
  144. let closestElement = imageElement.closest('._2rszc84L136gWQrkwH6IaM');
  145. if (!closestElement) {
  146. return;
  147. }
  148. let descendantElement = closestElement.querySelector('._1qeIAgB0cPwnLhDF9XSiJM');
  149. if (descendantElement) {
  150. console.log('SP3 execute');
  151. document.querySelectorAll('.specialElement').forEach(e => e.remove());
  152. let title = descendantElement.innerHTML;
  153. const specialContent = document.createElement('div');
  154. specialContent.className = 'specialElement';
  155. specialContent.style.cssText = 'position:fixed; bottom:80px; left:50%; transform:translateX(-50%); width:100vw; text-align:center; font-size:40px; color:white; text-shadow:0 0 10px rgba(255,255,255,0.7); z-index:99999;';
  156. specialContent.textContent = title;
  157. document.body.appendChild(specialContent);
  158.  
  159. var specialBackdrop = document.createElement('div');
  160. specialBackdrop.className = 'specialElement';
  161. specialBackdrop.style.cssText = 'position:fixed; bottom:60px; left:0; width:100vw; height:80px; background:rgba(0,0,0,0.2); backdrop-filter:blur(5px); z-index:99998;';
  162. document.body.appendChild(specialBackdrop);
  163.  
  164. } else {
  165. return;
  166. }
  167. }
  168. function SPL(imageElement) {
  169. document.querySelectorAll('.specialElement').forEach(e => e.remove());
  170.  
  171. let closestElement = imageElement.closest('.kcerW9lbT-se3SXd-wp2i');
  172. var element = closestElement.querySelector('._1fSFPkxZ9pToLETLQT2dmc');
  173. if (element) element.click();
  174.  
  175. let descendantElement = closestElement.querySelector('._15nNdGlBIgryHV04IfAfpA');
  176. if (descendantElement) {
  177. let title = descendantElement.getAttribute('title');
  178. const specialContent = document.createElement('div');
  179. specialContent.className = 'specialElement';
  180. specialContent.style.cssText = 'position:fixed; bottom:80px; left:50%; transform:translateX(-50%); width:100vw; text-align:center; font-size:40px; color:white; text-shadow:0 0 10px rgba(255,255,255,0.7); z-index:99999;';
  181. specialContent.textContent = title;
  182. document.body.appendChild(specialContent);
  183.  
  184. var specialBackdrop = document.createElement('div');
  185. specialBackdrop.className = 'specialElement';
  186. specialBackdrop.style.cssText = 'position:fixed; bottom:60px; left:0; width:100vw; height:80px; background:rgba(0,0,0,0.2); backdrop-filter:blur(5px); z-index:99998;';
  187. document.body.appendChild(specialBackdrop);
  188. } else {
  189. return;
  190. }
  191. }
  192. function SPR(imageElement) {
  193. document.querySelectorAll('.specialElement').forEach(e => e.remove());
  194.  
  195. let closestElement = imageElement.closest('.kcerW9lbT-se3SXd-wp2i');
  196. var element = closestElement.querySelector('._3-JCOd-nY76g29C7ZVX_kl:last-child');
  197. if (element) element.click();
  198.  
  199. let descendantElement = closestElement.querySelector('._15nNdGlBIgryHV04IfAfpA');
  200. if (descendantElement) {
  201. let title = descendantElement.getAttribute('title');
  202. const specialContent = document.createElement('div');
  203. specialContent.className = 'specialElement';
  204. specialContent.style.cssText = 'position:fixed; bottom:80px; left:50%; transform:translateX(-50%); width:100vw; text-align:center; font-size:40px; color:white; text-shadow:0 0 10px rgba(255,255,255,0.7); z-index:99999;';
  205. specialContent.textContent = title;
  206. document.body.appendChild(specialContent);
  207.  
  208. var specialBackdrop = document.createElement('div');
  209. specialBackdrop.className = 'specialElement';
  210. specialBackdrop.style.cssText = 'position:fixed; bottom:60px; left:0; width:100vw; height:80px; background:rgba(0,0,0,0.2); backdrop-filter:blur(5px); z-index:99998;';
  211. document.body.appendChild(specialBackdrop);
  212. } else {
  213. return;
  214. }
  215. }
  216.  
  217. // reddit: unblur spoiler, from https://greasyfork.org/en/scripts/479784-reddit-show-images-marked-with-spoiler-nsfw
  218. function SPunblurSpoiler(imageElement) {
  219. const regexIdImg = /https:\/\/.*\/(.*)\?/;
  220.  
  221. if (imageElement.src.includes('?blur=')) {
  222. const idImageElement = imageElement.src.match(regexIdImg)[1];
  223. imageElement.classList.remove('_3oBPn1sFwq76ZAxXgwRhhn');
  224. imageElement.src = `https://i.redd.it/${idImageElement}`;
  225. }
  226.  
  227. let descendantElement = imageElement.closest('.m3aNC6yp8RrNM_-a0rrfa');
  228. if (descendantElement) {
  229. descendantElement.style.background = '#0000';
  230. let removeElement = descendantElement.querySelector('._2iaYXFpGyyEGq1rp02cl5w');
  231. if (removeElement) {
  232. removeElement.style.display = 'none';
  233. }
  234. }
  235. }
  236.  
  237. //-------------------------------------------------------------------------
  238.  
  239.  
  240. // Configuration variables
  241. const currentHref = window.location.href;
  242. let enableP, positionP, intervalP, URLmatched;
  243. Object.keys(siteConfig).some((config) => {
  244. const regex = new RegExp(config);
  245. if (currentHref.match(regex)) {
  246. [enableP, positionP, intervalP] = siteConfig[config];
  247. URLmatched = true;
  248. return true;
  249. }
  250. });
  251.  
  252.  
  253. // The HoverZoomMinus Function---------------------------------------------
  254. function HoverZoomMinus() {
  255. let isshowPopupEnabled = true;
  256. isshowPopupEnabled = true;
  257.  
  258. const style = document.createElement('style');
  259. style.type = 'text/css';
  260. style.innerHTML = `
  261. .popup-container { display:none; cursor: move; z-index:1001; }
  262. .popup-image { max-height:calc(90vh - 10px); display:none; }
  263. .popup-backdrop { position:fixed; top:0; left:0; width:100vw; height:100vh; display:none; z-index:1000; }
  264. .centerBox { top:40px; left:40px; position:absolute; height:calc(100% - 80px); width:calc(100% - 80px); z-index:9999; }
  265. .TopBar, .BottomBar, .LeftBar, .RightBar {
  266. opacity:0; box-sizing:border-box; background:#0000; position:absolute; z-index:9999;
  267. &::after { content:""; position:absolute; }
  268. }
  269. .TopBar, .BottomBar { height:40px; width:calc(100% - 80px); left:40px;
  270. &::after { display:none; width:25px; height:25px; transform:rotate(45deg) translateX(-50%) translateY(-50%); }
  271. }
  272. .TopBar { top:0; }
  273. .BottomBar { bottom:0; }
  274. .TopBar::after { left:calc(50% - 17px); top:calc(50% + 15px); border-top:5px solid #6f8e9e; border-left:5px solid #6f8e9e; }
  275. .BottomBar::after { left:calc(50% - 2px); bottom:-8px; border-bottom:5px solid #6f8e9e; border-right: 5px solid #6f8e9e; }
  276. .LeftBar, .RightBar {
  277. height:calc(100% - 80px); width:40px; top:40px;
  278. &::before { content:""; display:block; position:absolute; top:50%; width:40px; height:40px; transform:translateY(-50%); border-radius:9999px; background: #242526; }
  279. &::after { content:""; display:block; position:absolute; top:calc(50% + -2px); width:15px; height:15px; }
  280. &:hover::before { display:none; }
  281. &:hover::after { width:25px; height:25px; border-color:#6f8e9e; border-width:5px; }
  282. }
  283. .LeftBar { left:0; }
  284. .RightBar { right:0; }
  285. .LeftBar::before { left:5px; opacity:0 }
  286. .RightBar::before { right:5px; opacity:0 }
  287. .LeftBar:hover::after { left:3px; }
  288. .RightBar:hover::after { right:3px; }
  289. .LeftBar::after { transform:rotate(45deg) translateY(-50%); border-bottom:3px solid #777; border-left:3px solid #777; left:12px; opacity:0 }
  290. .RightBar::after { transform:rotate(-45deg) translateY(-50%); border-bottom: 3px solid #777; border-right:3px solid #777; right:12px; opacity:0 }
  291. .popup-container:hover { .LeftBar::before, .RightBar::before, .LeftBar::after, .RightBar::after, .CornerBox.TR { opacity:1 }}
  292. .CornerBox { box-sizing:border-box; height:40px; width:40px; background:#0000; position:absolute; z-index:9999; }
  293. .CornerBox.TR { top:0; right:0; font-size:11px; text-align:center; line-height:25px; color:white; text-shadow:0 0 4px rgba(255,255,255,0.7); opacity:0 }
  294. .CornerBox.TL { top:0; left:0; }
  295. .CornerBox.BR { bottom:0; right:0; }
  296. .CornerBox.BL { bottom:0; left:0;}
  297. `;
  298.  
  299. document.head.appendChild(style);
  300. const backdrop = document.createElement('div');
  301. backdrop.className = 'popup-backdrop';
  302. document.body.appendChild(backdrop);
  303. const popupContainer = document.createElement('div');
  304. popupContainer.className = 'popup-container';
  305. document.body.appendChild(popupContainer);
  306. const popup = document.createElement('img');
  307. popup.className = 'popup-image';
  308. popupContainer.appendChild(popup);
  309.  
  310. const BottomBar = document.createElement('div');
  311. BottomBar.className = 'BottomBar';
  312. popupContainer.appendChild(BottomBar);
  313. const TopBar = document.createElement('div');
  314. TopBar.className = 'TopBar';
  315. popupContainer.appendChild(TopBar);
  316. const RightBar = document.createElement('div');
  317. RightBar.className = 'RightBar';
  318. popupContainer.appendChild(RightBar);
  319. const LeftBar = document.createElement('div');
  320. LeftBar.className = 'LeftBar';
  321. popupContainer.appendChild(LeftBar);
  322.  
  323. const TR = document.createElement('div');
  324. TR.className = 'CornerBox TR';
  325. popupContainer.appendChild(TR);
  326. const TL = document.createElement('div');
  327. TL.className = 'CornerBox TL';
  328. popupContainer.appendChild(TL);
  329. const BR = document.createElement('div');
  330. BR.className = 'CornerBox BR';
  331. popupContainer.appendChild(BR);
  332. const BL = document.createElement('div');
  333. BL.className = 'CornerBox BL';
  334. popupContainer.appendChild(BL);
  335.  
  336. const centerBox = document.createElement('div');
  337. centerBox.className = 'popup centerBox';
  338. popupContainer.appendChild(centerBox);
  339.  
  340. const style2 = document.createElement('style');
  341. style2.type = 'text/css';
  342. document.head.appendChild(style2);
  343.  
  344.  
  345.  
  346. //-------------------------------------------------------------------------
  347.  
  348. // Variable
  349. const ZOOM_SPEED = 0.005;
  350. let pageDirection;
  351. let isLockedY = false;
  352. let isLockedX = false;
  353. let scale = 1;
  354. let clickTimeout;
  355. let popupTimer;
  356. let isScale, isScaleTR, isScaleBR, isScaleTL, isScaleBL;
  357. let rect, rectT, rectH, rectL, rectW;
  358. let rectIH, rectIW, rectIT, rectIL, rectIRatio;
  359. let rectzT, rectzH, rectzL, rectzW, rectzRatio;
  360. let scaleCurrentX, scaleCurrentY;
  361. let scaleFactorX, scaleFactorY;
  362. let offsetX, offsetY, offsetXR, offsetYT, offsetXL, offsetYB;
  363. let offsetRatioY, offsetRatioX;
  364. let ishidePopupEnabled = true;
  365. let ScaleMode2 = false;
  366. let imgElementsList = [];
  367. let currentZeroImgElement;
  368. let rectIzRatio;
  369.  
  370. function NoMode() {
  371. isLockedY = false;
  372. isLockedX = false;
  373. isScale = false;
  374. isScaleTR = false;
  375. isScaleTL = false;
  376. isScaleBL = false;
  377. isScaleBR = false;
  378. popupContainer.style.border = '';
  379. TR.style.border = '';
  380. BR.style.border = '';
  381. TL.style.border = '';
  382. BL.style.border = '';
  383. }
  384.  
  385. function LockedYMode() {
  386. NoMode();
  387. isLockedY = true;
  388. popupContainer.style.borderLeft = '6px solid #00ff00';
  389. popupContainer.style.borderRight = '6px solid #00ff00';
  390. LeftBar.style.opacity = '0';
  391. RightBar.style.opacity = '0';
  392. LeftBar.style.display = '';
  393. RightBar.style.display = '';
  394. }
  395. function LockedXMode() {
  396. NoMode();
  397. LockedScreen();
  398. isLockedX = true;
  399. popupContainer.style.borderTop = '6px solid #00ff00';
  400. popupContainer.style.borderBottom = '6px solid #00ff00';
  401. LeftBar.style.opacity = '1';
  402. RightBar.style.opacity = '1';
  403. style2.innerHTML = `.LeftBar::before, .LeftBar::after, .RightBar::before, .RightBar::after { display:block; }`;
  404. }
  405.  
  406. function toggleLockedScreen(event) {
  407. ishidePopupEnabled = !ishidePopupEnabled;
  408. popupContainer.style.outline = ishidePopupEnabled ? '' : '6px solid #ae0001';
  409. }
  410.  
  411. function LockedScreen() {
  412. ishidePopupEnabled = false;
  413. popupContainer.style.outline = '6px solid #ae0001';
  414. }
  415.  
  416. function ScalingMode1() {
  417. TL.style.borderTop = '6px solid #0000ff';
  418. TR.style.borderTop = '6px solid #0000ff';
  419. TL.style.borderLeft = '6px solid #0000ff';
  420. BL.style.borderLeft = '6px solid #0000ff';
  421. TR.style.borderRight = '6px solid #0000ff';
  422. BR.style.borderRight = '6px solid #0000ff';
  423. BR.style.borderBottom = '6px solid #0000ff';
  424. BL.style.borderBottom = '6px solid #0000ff';
  425. }
  426. function ScalingMode2() {
  427. ScalingMode1();
  428. TL.style.borderColor = '#ff00ff';
  429. TR.style.borderColor = '#ff00ff';
  430. BL.style.borderColor = '#ff00ff';
  431. BR.style.borderColor = '#ff00ff';
  432. }
  433. function ScalingMode0() {
  434. TL.style.border = '';
  435. TR.style.border = '';
  436. BL.style.border = '';
  437. BR.style.border = '';
  438. }
  439.  
  440. function ResetGeometry() {
  441. let rectF = popup.getBoundingClientRect();
  442. rectzH = rectF.height;
  443. rectzW = rectF.width;
  444. rectzT = rectF.top;
  445. rectzL = rectF.left;
  446. popup.style.maxHeight = rectzH + 'px';
  447. popup.style.height = rectzH + 'px';
  448. popup.style.width = rectzW + 'px';
  449. popupContainer.style.top = rectzT + (rectzH / 2) + 'px';
  450. popupContainer.style.left = rectzL + (rectzW / 2) + 'px';
  451. popupContainer.style.transformOrigin = '50% 50% 0px';
  452. popupContainer.style.transform = `translate(-50%, -50%) scaleX(1) scaleY(1)`;
  453. }
  454.  
  455. function BarClear() {
  456. TL.style.border = '';
  457. TR.style.border = '';
  458. BL.style.border = '';
  459. BR.style.border = '';
  460. TL.style.background = '';
  461. TR.style.background = '';
  462. BL.style.background = '';
  463. BR.style.background = '';
  464. TopBar.style.background = '';
  465. LeftBar.style.background = '';
  466. RightBar.style.background = '';
  467. BottomBar.style.background = '';
  468. popupContainer.style.border = '';
  469. LeftBar.style.opacity = '0';
  470. RightBar.style.opacity = '0';
  471. }
  472.  
  473.  
  474. const loadingIcon = document.createElement('div');
  475.  
  476. function NavigateAlbum() {
  477. let pair = albumSelector.find(pair => currentZeroImgElement.matches(pair.imgElement));
  478. if (pair) {
  479. let ancestorElement = currentZeroImgElement.closest(pair.albumElements);
  480. if (ancestorElement) {
  481. imgElementsList = Array.from(ancestorElement.querySelectorAll(pair.imgElement));
  482. let zeroIndex = imgElementsList.indexOf(currentZeroImgElement);
  483. let direction = pageDirection;
  484. let newIndex = zeroIndex + direction;
  485. TR.textContent = `${newIndex + 1}/${imgElementsList.length}`;
  486.  
  487.  
  488. document.querySelectorAll('.loadingIcon').forEach(e => e.remove())
  489.  
  490.  
  491. if (newIndex <= 0) {
  492. LeftBar.style.display = 'none';
  493. TR.textContent = `1/${imgElementsList.length}`;
  494. } else {
  495. LeftBar.style.display = '';
  496. }
  497. if (newIndex >= imgElementsList.length - 1) {
  498. RightBar.style.display = 'none';
  499. TR.textContent = `${imgElementsList.length}/${imgElementsList.length}`;
  500. } else {
  501. RightBar.style.display = '';
  502. }
  503. if (newIndex < 0 || newIndex >= imgElementsList.length) {
  504. return;
  505. }
  506.  
  507. const loadingIcon = document.createElement('div');
  508. loadingIcon.className = 'loadingIcon'
  509. popupContainer.appendChild(loadingIcon);
  510. loadingIcon.style.display = 'none';
  511. loadingIcon.style.transition = 'transform 0.1s linear';
  512. loadingIcon.style.cssText = 'position:absolute; transform:translateZ(0); z-index:9999; font-size:55px; height:40px; width:40px; line-height:40px; top:calc(50% - 20px); left:calc(50% - 20px); background:url("chrome://global/skin/icons/reload.svg"); background-repeat:no-repeat; background-size:contain; filter:invert(.8);';
  513. loadingIcon.style.color = '#0000';;
  514. currentZeroImgElement = imgElementsList[newIndex];
  515. var img = new Image();
  516. function rotateLoadingIcon() {
  517. var angle = parseFloat(loadingIcon.getAttribute('data-angle')) || 0;
  518. angle += 27;
  519. loadingIcon.style.transform = 'rotate(' + angle + 'deg)';
  520. loadingIcon.setAttribute('data-angle', angle);
  521. loadingIcon.style.color = '#777';
  522. }
  523. var rotationInterval = setInterval(rotateLoadingIcon, 100);
  524.  
  525. img.onload = function() {
  526.  
  527. document.querySelectorAll('.loadingIcon').forEach(e => e.remove())
  528. clearInterval(rotationInterval);
  529.  
  530. let natHeight = img.naturalHeight;
  531. let vh = window.innerHeight / 100;
  532. let rect = popup.getBoundingClientRect();
  533. let rectH = rect.height;
  534. if ((natHeight > ((90 * vh) - 10)) || (rectH > ((90 * vh) - 10))) {
  535. popup.style.maxHeight = (90 * vh) - 10 + 'px';
  536. popup.style.width = '';
  537. popupContainer.style.top = '50%';
  538. } else {
  539. popup.style.height = '';
  540. popup.style.maxHeight = 'unset';
  541. }
  542. };
  543.  
  544. img.onerror = function() {
  545. loadingIcon.style.display = 'none';
  546. clearInterval(rotationInterval);
  547. };
  548. loadingIcon.style.display = 'block';
  549.  
  550. popup.src = currentZeroImgElement.src;
  551. img.src = popup.src;
  552. }
  553. }
  554. }
  555.  
  556. // Mouse Click: Center
  557. centerBox.addEventListener('click', function(event) {
  558. if (clickTimeout) clearTimeout(clickTimeout);
  559. clickTimeout = setTimeout(function() {
  560. ResetGeometry();
  561. if (!isScale) {
  562. isLockedY = !isLockedY;
  563. }
  564. if (isLockedY) {
  565. LockedYMode();
  566. let rect = popupContainer.getBoundingClientRect();
  567. offsetX = event.clientX - rect.left - (rect.width / 2);
  568. offsetY = event.clientY - rect.top - (rect.height / 2);
  569. } else {
  570. NoMode();
  571. }
  572. }, 300);
  573. });
  574.  
  575. centerBox.addEventListener('dblclick', function(event) {
  576. clearTimeout(clickTimeout);
  577. toggleLockedScreen();
  578. if (!ishidePopupEnabled) {
  579. backdrop.style.display = 'block';
  580. backdrop.style.zIndex = '999';
  581. backdrop.style.backdropFilter = 'blur(10px)';
  582. }
  583. });
  584. //-------------------------------------------------------------------------
  585.  
  586. // Mouse Click: Corners
  587. document.querySelectorAll('.CornerBox').forEach(element => {
  588. element.addEventListener('click', function(event) {
  589. ishidePopupEnabled = false;
  590. if (isScale) {
  591. BarClear();
  592. isLockedY = false;
  593. isLockedX = false;
  594. popup.style.border = '';
  595.  
  596. const clickedElement = event.target;
  597. const popupContainer = clickedElement.parentElement;
  598. const currentTransform = window.getComputedStyle(popupContainer).transform;
  599. const matrixMatch = currentTransform.match(/^matrix\(([^,]+), [^,]+, [^,]+, ([^,]+), [^,]+, [^,]+\)$/);
  600. if (matrixMatch) {
  601. scaleCurrentX = parseFloat(matrixMatch[1]);
  602. scaleCurrentY = parseFloat(matrixMatch[2]);
  603. }
  604.  
  605. let rect = popupContainer.getBoundingClientRect();
  606. offsetYT = event.clientY - rect.top;
  607. offsetXL = event.clientX - rect.left;
  608. offsetYB = rect.height + rect.top - event.clientY;
  609. offsetXR = rect.width + rect.left - event.clientX;
  610.  
  611. rectT = rect.top;
  612. rectL = rect.left;
  613. rectH = rect.height;
  614. rectW = rect.width;
  615.  
  616. } else {
  617. LockedScreen();
  618. ResetGeometry();
  619. }
  620.  
  621. });
  622. });
  623. document.querySelectorAll('.CornerBox').forEach(element => {
  624. element.addEventListener('click', function(event) {
  625. if (clickTimeout) clearTimeout(clickTimeout);
  626. isScale = !isScale;
  627. }, 300);
  628. });
  629. document.querySelectorAll('.CornerBox').forEach(element => {
  630. element.addEventListener('dblclick', function(event) {
  631. clearTimeout(clickTimeout);
  632. ScaleMode2 = !ScaleMode2;
  633. });
  634. });
  635.  
  636. TL.addEventListener('click', function(event) {
  637. if (clickTimeout) clearTimeout(clickTimeout);
  638. clickTimeout = setTimeout(function() {
  639. isScaleTL = !isScaleTL;
  640. isScaleTR = false;
  641. isScaleBL = false;
  642. isScaleBR = false;
  643. LeftBar.style.display = '';
  644. RightBar.style.display = '';
  645. }, 300);
  646. });
  647. TR.addEventListener('click', function(event) {
  648. if (clickTimeout) clearTimeout(clickTimeout);
  649. clickTimeout = setTimeout(function() {
  650. isScaleTL = false;
  651. isScaleTR = !isScaleTR;
  652. isScaleBL = false;
  653. isScaleBR = false;
  654. LeftBar.style.display = '';
  655. RightBar.style.display = '';
  656. }, 300);
  657. });
  658. BL.addEventListener('click', function(event) {
  659. if (clickTimeout) clearTimeout(clickTimeout);
  660. clickTimeout = setTimeout(function() {
  661. isScaleTL = false;
  662. isScaleTR = false;
  663. isScaleBL = !isScaleBL;
  664. isScaleBR = false;
  665. LeftBar.style.display = '';
  666. RightBar.style.display = '';
  667. }, 300);
  668. });
  669. BR.addEventListener('click', function(event) {
  670. if (clickTimeout) clearTimeout(clickTimeout);
  671. clickTimeout = setTimeout(function() {
  672. isScaleTL = false;
  673. isScaleTR = false;
  674. isScaleBL = false;
  675. isScaleBR = !isScaleBR;
  676. LeftBar.style.display = '';
  677. RightBar.style.display = '';
  678. }, 300);
  679. });
  680. //-------------------------------------------------------------------------
  681.  
  682.  
  683. // Mouse Move: Pan, Scale
  684. document.addEventListener('mousemove', function(event) {
  685. // Panning mode: popup locked and follows the mouse
  686. if (isLockedY) {
  687. popupContainer.style.left = (event.clientX - offsetX) + 'px';
  688. popupContainer.style.top = (event.clientY - offsetY) + 'px';
  689.  
  690. } else if (isScale) {
  691. ScalingMode1();
  692. if (ScaleMode2) {
  693. ScalingMode2();
  694. }
  695.  
  696. if (isScaleTL) {
  697. popupContainer.style.transformOrigin = '100% 100% 0px';
  698. scaleFactorY = scaleCurrentY * (1 + (rectT - event.clientY + offsetYT) / rectH);
  699. scaleFactorX = scaleCurrentX * (1 + (rectL - event.clientX + offsetXL) / rectW);
  700. } else if (isScaleTR) {
  701. popupContainer.style.transformOrigin = '0 100% 0px';
  702. scaleFactorY = scaleCurrentY * (1 + (rectT - event.clientY + offsetYT) / rectH);
  703. scaleFactorX = scaleCurrentX * ((event.clientX - rectL + offsetXR) / rectW);
  704. } else if (isScaleBL) {
  705. popupContainer.style.transformOrigin = '100% 0% 0px';
  706. scaleFactorY = scaleCurrentY * ((event.clientY - rectT + offsetYB) / rectH);
  707. scaleFactorX = scaleCurrentX * (1 + (rectL - event.clientX + offsetXL) / rectW);
  708. } else if (isScaleBR) {
  709. popupContainer.style.transformOrigin = '0% 0% 0px';
  710. scaleFactorY = scaleCurrentY * ((event.clientY - rectT + offsetYB) / rectH);
  711. scaleFactorX = scaleCurrentX * ((event.clientX - rectL + offsetXR) / rectW);
  712. }
  713. if (ScaleMode2) {
  714. popupContainer.style.transform = `translate(-50%, -50%) scaleX(${scaleFactorX}) scaleY(${scaleFactorY})`;
  715.  
  716. } else {
  717. popupContainer.style.transform = `translate(-50%, -50%) scaleX(${scaleFactorX}) scaleY(${scaleFactorX})`;
  718. }
  719. }
  720. });
  721.  
  722.  
  723. // Mouse Wheel: Zoom, Scroll, Navigate
  724. function ZoomOrScroll(event) {
  725. event.preventDefault();
  726. if (isLockedY) {
  727. let deltaY = event.deltaY * -ZOOM_SPEED;
  728. let newTop = parseInt(popupContainer.style.top) || 0;
  729. newTop += deltaY * 100;
  730. popupContainer.style.top = newTop + 'px';
  731. offsetY -= deltaY * 100;
  732.  
  733. } else if (isLockedX && currentZeroImgElement) {
  734. pageDirection = event.deltaY > 0 ? 1 : -1;
  735. NavigateAlbum();
  736.  
  737. if (pageDirection === 1) {
  738. const imageElement = currentContainer.querySelector(imgElements);
  739. specialRightBar.forEach(pair => {
  740. if (imageElement.matches(pair.selector)) {
  741. pair.func(imageElement);
  742. }
  743. });
  744. } else {
  745. const imageElement = currentContainer.querySelector(imgElements);
  746. specialLeftBar.forEach(pair => {
  747. if (imageElement.matches(pair.selector)) {
  748. pair.func(imageElement);
  749. }
  750. });
  751. }
  752.  
  753. } else {
  754. scale += event.deltaY * -ZOOM_SPEED;
  755. scale = Math.min(Math.max(0.125, scale), 10);
  756. popupContainer.style.transform = `translate(-50%, -50%) scaleX(${scale}) scaleY(${scale})`;
  757. }
  758. }
  759. popupContainer.addEventListener('wheel', ZoomOrScroll);
  760. //-------------------------------------------------------------------------
  761.  
  762.  
  763. // Bottom Bar: Album
  764. BottomBar.addEventListener('mouseenter', function(e) {
  765.  
  766. if (isScale) {
  767. return;
  768. } else {
  769. BottomBar.style.opacity = '1';
  770.  
  771. let rect = popup.getBoundingClientRect();
  772. rectzRatio = rect.height / rect.width;
  773. rectIzRatio = Number(rectIRatio.toFixed(3)) / Number(rectzRatio.toFixed(3));
  774.  
  775. if (isAlbum) {
  776. if (!isScale) {
  777. BottomBar.style.background = 'linear-gradient(to right, rgba(0, 0, 255, 0) 0%, rgba(0, 0, 255, 0.5) 25%, rgba(0, 0, 255, 0.5) 75%, rgba(0, 0, 255, 0) 100%)';
  778. }
  779. } else {
  780. BottomBar.style.background = '';
  781. if (!( (rectIzRatio === 1) || isScale ) ) {
  782. style2.innerHTML = `
  783. .BottomBar::after {
  784. display: block;
  785. }
  786. `;
  787. BL.style.background = 'rgba(0, 0, 0, 0.5)';
  788. BR.style.background = 'rgba(0, 0, 0, 0.5)';
  789. BottomBar.style.background = 'rgba(0, 0, 0, 0.5)';
  790. } else {
  791. style2.innerHTML = `
  792. .BottomBar::after {
  793. display: none;
  794. }
  795. `;
  796. }
  797. }
  798. }
  799. });
  800.  
  801. BottomBar.addEventListener('mouseleave', function() {
  802. BottomBar.style.opacity = '0';
  803. if (!isLockedX) {
  804. BarClear();
  805. }
  806. });
  807.  
  808. BottomBar.addEventListener('click', function(event) {
  809. if (clickTimeout) clearTimeout(clickTimeout);
  810. clickTimeout = setTimeout(function() {
  811. if (!isScale) {
  812. LeftBar.style.opacity = '0';
  813. RightBar.style.opacity = '0';
  814. }
  815. if (isAlbum) {
  816. ResetGeometry();
  817. var img = new Image();
  818. img.onload = function() {
  819. natHeight = img.naturalHeight;
  820. natWidth = img.naturalWidth;
  821. };
  822. img.src = popup.src
  823. rectIRatio = natHeight / natWidth;
  824. isLockedX = !isLockedX;
  825. if (isLockedX) {
  826. LockedXMode();
  827. LeftBar.style.opacity = '1';
  828. RightBar.style.opacity = '1';
  829. } else {
  830. popupContainer.style.border = '';
  831. LeftBar.style.display = '';
  832. RightBar.style.display = '';
  833. }
  834. let pair = albumSelector.find(pair => currentZeroImgElement.matches(pair.imgElement));
  835. if (pair) {
  836. let ancestorElement = currentZeroImgElement.closest(pair.albumElements);
  837. if (ancestorElement) {
  838. imgElementsList = Array.from(ancestorElement.querySelectorAll(pair.imgElement));
  839. let zeroIndex = imgElementsList.indexOf(currentZeroImgElement);
  840. if (zeroIndex <= 0) {
  841. LeftBar.style.display = 'none';
  842. } else {
  843. LeftBar.style.display = '';
  844. }
  845. if (zeroIndex >= imgElementsList.length - 1) {
  846. RightBar.style.display = 'none';
  847. } else {
  848. RightBar.style.display = '';
  849. }
  850. }
  851. }
  852. } else {
  853. ResetGeometry();
  854. if (isScale) {
  855. isScale = false;
  856. popup.style.maxHeight = rectzH + 'px';
  857. popup.style.height = rectzH + 'px';
  858. offsetRatioY = 0;
  859. } else {
  860. popup.style.maxHeight = 'unset';
  861. popup.style.height = '';
  862. offsetRatioY = (rectzH - rectzW * rectIRatio) / 2 ;
  863. }
  864. popupContainer.style.top = rectzT + (rectzH / 2) + offsetRatioY + 'px';
  865. }
  866. }, 300);
  867. });
  868.  
  869. BottomBar.addEventListener('dblclick', function(event) {
  870. clearTimeout(clickTimeout);
  871. if (isAlbum) {
  872. ResetGeometry();
  873. var img = new Image();
  874. img.onload = function() {
  875. natHeight = img.naturalHeight;
  876. natWidth = img.naturalWidth;
  877. };
  878. img.src = popup.src
  879. rectIRatio = natHeight / natWidth;
  880. if (isScale) {
  881. popup.style.maxHeight = rectzH + 'px';
  882. popup.style.height = rectzH + 'px';
  883. offsetRatioY = 0;
  884. } else {
  885. popup.style.maxHeight = 'unset';
  886. popup.style.height = '';
  887. offsetRatioY = (rectzH - rectzW * rectIRatio) / 2 ;
  888. }
  889. popupContainer.style.top = rectzT + (rectzH / 2) + offsetRatioY + 'px';
  890. BarClear();
  891. } else {
  892. return;
  893. }
  894. });
  895. //-------------------------------------------------------------------------
  896.  
  897.  
  898. // Indicators
  899. document.querySelectorAll('.CornerBox').forEach(element => {
  900. element.addEventListener('mouseenter', function(event) {
  901. ScalingMode1();
  902. if (ScaleMode2) {
  903. ScalingMode2();
  904. }
  905. });
  906. element.addEventListener('mouseleave', function(event) {
  907. if (!isScale) {
  908. ScalingMode0();
  909. }
  910. });
  911. });
  912.  
  913. // Re-scale/ Navigate
  914. TopBar.addEventListener('click', function(event) {
  915. if (clickTimeout) clearTimeout(clickTimeout);
  916. clickTimeout = setTimeout(function() {
  917. ResetGeometry();
  918. if (isAlbum) {
  919. var img = new Image();
  920. img.onload = function() {
  921. natHeight = img.naturalHeight;
  922. natWidth = img.naturalWidth;
  923. };
  924. img.src = popup.src
  925. rectIRatio = natHeight / natWidth;
  926. }
  927. if (isScale) {
  928. isScale = false;
  929. popup.style.maxHeight = rectzH + 'px';
  930. popup.style.height = rectzH + 'px';
  931. offsetRatioY = 0;
  932. } else {
  933. popup.style.maxHeight = 'unset';
  934. popup.style.height = '';
  935. offsetRatioY = (rectzH - rectzW * rectIRatio) / 2 ;
  936. }
  937. popupContainer.style.top = rectzT + (rectzH / 2) - offsetRatioY + 'px';
  938. BarClear();
  939.  
  940. }, 300);
  941. });
  942.  
  943. const styleCrop = document.createElement('style');
  944. styleCrop.type = 'text/css';
  945. styleCrop.innerHTML = `.cropArea1,.cropArea2,.cropArea3,.cropArea4 { display:none; }`;
  946. const CropCover = document.createElement('div');
  947. const cropArea1 = document.createElement('div');
  948. const cropArea2 = document.createElement('div');
  949. const cropArea3 = document.createElement('div');
  950. const cropArea4 = document.createElement('div');
  951.  
  952. let clickX1, clickY1, clickX2, clickY2;
  953. let clickCount;
  954. let cropAreaSet = false;
  955.  
  956. TopBar.addEventListener('dblclick', function(event) {
  957. clearTimeout(window.clickTimeout);
  958.  
  959. LockedScreen();
  960. clickCount = 0;
  961. ResetGeometry()
  962. cropAreaSet = false;
  963.  
  964. const CropCover = document.createElement('div');
  965. const cropArea1 = document.createElement('div');
  966. const cropArea2 = document.createElement('div');
  967. const cropArea3 = document.createElement('div');
  968. const cropArea4 = document.createElement('div');
  969. CropCover.className = 'CropCover'
  970. cropArea1.className = 'cropArea1';
  971. cropArea2.className = 'cropArea2';
  972. cropArea3.className = 'cropArea3';
  973. cropArea4.className = 'cropArea4';
  974. popupContainer.appendChild(CropCover);
  975. document.body.appendChild(cropArea1);
  976. document.body.appendChild(cropArea2);
  977. document.body.appendChild(cropArea3);
  978. document.body.appendChild(cropArea4);
  979. CropCover.style.cssText = 'position:absolute; transform:translateZ(0); top:0; left:0; box-sizing:border-box; height:100%; width:100%; z-index:99999; border:1px solid yellow;';
  980. cropArea1.style.cssText ='display: block; z-index: 9999; position: fixed; background: rgba(0, 0, 0, 0.4);';
  981. cropArea2.style.cssText ='display: block; z-index: 9999; position: fixed; background: rgba(0, 0, 0, 0.4);';
  982. cropArea3.style.cssText ='display: block; z-index: 9999; position: fixed; background: rgba(0, 0, 0, 0.4);';
  983. cropArea4.style.cssText ='display: block; z-index: 9999; position: fixed; background: rgba(0, 0, 0, 0.4);';
  984.  
  985. let rect = popup.getBoundingClientRect();
  986. rectzH = rect.height;
  987. rectzW = rect.width;
  988. rectzT = rect.top;
  989. rectzL = rect.left;
  990.  
  991. let clickTimeout;
  992.  
  993. CropCover.addEventListener('click', function(e) {
  994. if (clickCount === 0) {
  995. clickX1 = e.clientX;
  996. clickY1 = e.clientY;
  997. clickCount = 1;
  998. } else if (clickCount === 1) {
  999. clickCount = 2;
  1000. clickX2 = e.clientX;
  1001. clickY2 = e.clientY;
  1002. cropAreaSet = true;
  1003. if (clickCount === 2) {
  1004. cropImage();
  1005. }
  1006. }
  1007. });
  1008.  
  1009. document.addEventListener('mousemove', function(event) {
  1010.  
  1011. cropArea1.style.left = '0px';
  1012. cropArea1.style.top = '0px';
  1013. cropArea1.style.height = '100vh';
  1014.  
  1015. cropArea2.style.top = '0px';
  1016.  
  1017.  
  1018. if (clickCount === 0) {
  1019. cropArea1.style.width = event.clientX + 'px';
  1020.  
  1021. cropArea2.style.left = event.clientX + 'px';
  1022. cropArea2.style.height = event.clientY + 'px';
  1023. cropArea2.style.width = `calc(100vw - ${event.clientX}px)`;
  1024.  
  1025.  
  1026. } else if (clickCount === 1) {
  1027. cropArea1.style.width = clickX1 + 'px';
  1028.  
  1029. cropArea2.style.left = clickX1 + 'px';
  1030. cropArea2.style.height = clickY1 + 'px';
  1031. cropArea2.style.width = `calc(100vw - ${clickX1}px)`;
  1032.  
  1033.  
  1034. }
  1035. if (!cropAreaSet && (clickCount === 1)) {
  1036. cropArea3.style.left = clickX1 + 'px';
  1037. cropArea3.style.top = event.clientY + 1 + 'px';
  1038. cropArea3.style.width = `calc(${event.clientX}px + 1px - ${clickX1}px)`;
  1039. cropArea3.style.height = `calc(100vh - 1px - ${event.clientY}px)`;
  1040.  
  1041. cropArea4.style.left = event.clientX + 1 + 'px';
  1042. cropArea4.style.top = clickY1 + 'px'
  1043. cropArea4.style.height = `calc(100vh - 1px - ${clickY1}px)`;
  1044. cropArea4.style.width = `calc(100vw - 1px - ${event.clientX}px)`;
  1045.  
  1046.  
  1047. }
  1048. });
  1049.  
  1050. });
  1051.  
  1052. function cropImage() {
  1053. var canvas = document.createElement('canvas');
  1054. canvas.id = 'cropCanvas';
  1055. var ctx = canvas.getContext('2d');
  1056. document.body.appendChild(canvas);
  1057. let cropX, cropY, cropW, cropH, scW, scH
  1058.  
  1059. scW = clickX2 - clickX1
  1060. scH = clickY2 - clickY1
  1061. cropX = natWidth * ((clickX1 - rectzL) / rectzW);
  1062. cropY = natHeight * ((clickY1 - rectzT) / rectzH);
  1063. cropW = natWidth * (scW / rectzW);
  1064. cropH = natHeight * (scH / rectzH);
  1065. canvas.width = scW;
  1066. canvas.height = scH;
  1067.  
  1068. var image = new Image();
  1069. image.crossOrigin = "anonymous";
  1070. image.src = popup.src
  1071. image.onload = function() {
  1072. ctx.drawImage(image, cropX, cropY, cropW, cropH, 0, 0, scW, scH);
  1073. saveImage(canvas);
  1074. }
  1075.  
  1076. document.querySelectorAll('.CropCover').forEach(e => e.remove())
  1077. }
  1078.  
  1079. function saveImage(canvas) {
  1080. if (confirm('Do you want to save the image?')) {
  1081. var croppedImageDataURL = canvas.toDataURL('image/png');
  1082. var originalFileName = popup.src.split('/').pop();
  1083. var truncatedFileName = originalFileName.length > 100 ? originalFileName.substring(0, 100) : originalFileName;
  1084. var downloadLink = document.createElement('a');
  1085. downloadLink.href = croppedImageDataURL;
  1086. downloadLink.download = truncatedFileName;
  1087. document.body.appendChild(downloadLink);
  1088. downloadLink.click();
  1089. }
  1090.  
  1091. clickCount = 0;
  1092. cropAreaSet = false;
  1093. document.querySelectorAll('.cropArea1').forEach(e => e.remove())
  1094. document.querySelectorAll('.cropArea2').forEach(e => e.remove())
  1095. document.querySelectorAll('.cropArea3').forEach(e => e.remove())
  1096. document.querySelectorAll('.cropArea4').forEach(e => e.remove())
  1097. document.body.removeChild(downloadLink);
  1098. var removecropCanvas = document.getElementById('cropCanvas');
  1099. removecropCanvas.remove();
  1100.  
  1101. }
  1102.  
  1103.  
  1104. let zeroIndex;
  1105. LeftBar.addEventListener('click', function(event) {
  1106. if (clickTimeout) clearTimeout(clickTimeout);
  1107. clickTimeout = setTimeout(function() {
  1108. ResetGeometry();
  1109. if (isAlbum) {
  1110. var img = new Image();
  1111. img.onload = function() {
  1112. natHeight = img.naturalHeight;
  1113. natWidth = img.naturalWidth;
  1114. };
  1115. img.src = popup.src
  1116. rectIRatio = natHeight / natWidth;
  1117. }
  1118. if (isScale) {
  1119. isScale = false;
  1120. popup.style.width = rectzW + 'px';
  1121. offsetRatioX = 0;
  1122. } else {
  1123. popup.style.width = '';
  1124. offsetRatioX = (rectzW - (rectzH / rectIRatio)) / 2 ;
  1125. }
  1126. if (isLockedX && currentZeroImgElement) {
  1127. pageDirection = -1;
  1128. NavigateAlbum();
  1129. TL.style.background = 'rgba(0, 0, 0, 0.5)';
  1130. BL.style.background = 'rgba(0, 0, 0, 0.5)';
  1131. LeftBar.style.background = 'rgba(0, 0, 0, 0.5)';
  1132.  
  1133. const imageElement = currentContainer.querySelector(imgElements);
  1134. specialLeftBar.forEach(pair => {
  1135. if (imageElement.matches(pair.selector)) {
  1136. pair.func(imageElement);
  1137. }
  1138. });
  1139. } else {
  1140. popupContainer.style.left = rectzL + (rectzW / 2) - offsetRatioX + 'px';
  1141. }
  1142. }, 300);
  1143. });
  1144. RightBar.addEventListener('click', function(event) {
  1145. if (clickTimeout) clearTimeout(clickTimeout);
  1146. clickTimeout = setTimeout(function() {
  1147. ResetGeometry();
  1148. if (isAlbum) {
  1149. var img = new Image();
  1150. img.onload = function() {
  1151. natHeight = img.naturalHeight;
  1152. natWidth = img.naturalWidth;
  1153. };
  1154. img.src = popup.src
  1155. rectIRatio = natHeight / natWidth;
  1156. }
  1157. if (isScale) {
  1158. isScale = false;
  1159. popup.style.width = rectzW + 'px';
  1160. offsetRatioX = 0;
  1161. } else {
  1162. popup.style.width = '';
  1163. offsetRatioX = (rectzW - (rectzH / rectIRatio)) / 2 ;
  1164. }
  1165. if (isLockedX && currentZeroImgElement) {
  1166. pageDirection = 1;
  1167. NavigateAlbum();
  1168. TR.style.background = 'rgba(0, 0, 0, 0.5)';
  1169. BR.style.background = 'rgba(0, 0, 0, 0.5)';
  1170. RightBar.style.background = 'rgba(0, 0, 0, 0.5)';
  1171.  
  1172. const imageElement = currentContainer.querySelector(imgElements);
  1173. specialRightBar.forEach(pair => {
  1174. if (imageElement.matches(pair.selector)) {
  1175. pair.func(imageElement);
  1176. }
  1177. });
  1178. } else {
  1179. popupContainer.style.left = rectzL + (rectzW / 2) + offsetRatioX + 'px';
  1180. }
  1181. }, 300);
  1182. });
  1183.  
  1184. TopBar.addEventListener('mouseenter', function() {
  1185.  
  1186. var img = new Image();
  1187. img.onload = function() {
  1188. natHeight = img.naturalHeight;
  1189. natWidth = img.naturalWidth;
  1190. };
  1191. img.src = popup.src
  1192. let rectZ = popup.getBoundingClientRect();
  1193. let rectZH = rectZ.height;
  1194. let rectZW = rectZ.width;
  1195. let rectZRatio = rectZH / rectZW;
  1196. let natRatio = natHeight / natWidth;
  1197. let rectNZRatio = Number(natRatio.toFixed(3)) / Number(rectZRatio.toFixed(3));
  1198. // not original Ratio and not scaling mode aplly with arrow indicator
  1199. if (!((rectNZRatio === 1) || isScale )) {
  1200.  
  1201. TopBar.style.opacity = '1';
  1202. TL.style.background = 'rgba(0, 0, 0, 0.5)';
  1203. TR.style.background = 'rgba(0, 0, 0, 0.5)';
  1204. TopBar.style.background = 'rgba(0, 0, 0, 0.5)';
  1205.  
  1206. style2.innerHTML = `.TopBar::after { display: block; }`;
  1207.  
  1208. } else {
  1209. style2.innerHTML = `.TopBar::after { display: none; }`;
  1210. }
  1211. });
  1212. LeftBar.addEventListener('mouseenter', function() {
  1213. LeftBar.style.opacity = '1';
  1214. let rect = popup.getBoundingClientRect();
  1215. rectzRatio = rect.height / rect.width;
  1216. rectIzRatio = Number(rectIRatio.toFixed(3)) / Number(rectzRatio.toFixed(3));
  1217. if ( isLockedX || !( (rectIzRatio === 1) || isScale ) ) {
  1218. style2.innerHTML = `
  1219. .LeftBar::before, .LeftBar::after {
  1220. display: block;
  1221. }`;
  1222. TL.style.background = 'rgba(0, 0, 0, 0.5)';
  1223. BL.style.background = 'rgba(0, 0, 0, 0.5)';
  1224. LeftBar.style.background = 'rgba(0, 0, 0, 0.5)';
  1225. } else {
  1226. style2.innerHTML = `
  1227. .LeftBar::before, .LeftBar::after {
  1228. display: none;
  1229. }`;
  1230. }
  1231. });
  1232. RightBar.addEventListener('mouseenter', function() {
  1233. RightBar.style.opacity = '1';
  1234. let rect = popup.getBoundingClientRect();
  1235. rectzRatio = rect.height / rect.width;
  1236. rectIzRatio = Number(rectIRatio.toFixed(3)) / Number(rectzRatio.toFixed(3));
  1237. if ( isLockedX || !( (rectIzRatio === 1) || isScale ) ) {
  1238. style2.innerHTML = `
  1239. .RightBar::before, .RightBar::after {
  1240. display: block;
  1241. }`;
  1242. TR.style.background = 'rgba(0, 0, 0, 0.5)';
  1243. BR.style.background = 'rgba(0, 0, 0, 0.5)';
  1244. RightBar.style.background = 'rgba(0, 0, 0, 0.5)';
  1245. } else {
  1246. style2.innerHTML = `
  1247. .RightBar::before, .RightBar::after {
  1248. display: none;
  1249. }`;
  1250. }
  1251. });
  1252.  
  1253. TopBar.addEventListener('mouseleave', function() {
  1254. TopBar.style.opacity = '0';
  1255. TL.style.background = '';
  1256. TR.style.background = '';
  1257. });
  1258. LeftBar.addEventListener('mouseleave', function() {
  1259. if (isLockedX) {
  1260. LeftBar.style.opacity = '1';
  1261. } else {
  1262. LeftBar.style.opacity = '0';
  1263. }
  1264. TL.style.background = '';
  1265. BL.style.background = '';
  1266. LeftBar.style.background = '';
  1267. });
  1268. RightBar.addEventListener('mouseleave', function() {
  1269. if (isLockedX) {
  1270. RightBar.style.opacity = '1';
  1271. } else {
  1272. RightBar.style.opacity = '0';
  1273. }
  1274. TR.style.background = '';
  1275. BR.style.background = '';
  1276. RightBar.style.background = '';
  1277. });
  1278. //-------------------------------------------------------------------------
  1279.  
  1280. let natXratio, natYratio, natHeight, natWidth;
  1281.  
  1282. // show popup
  1283. function showPopup(src, mouseX, mouseY) {
  1284. console.log('isshowPopupEnabled:', isshowPopupEnabled);
  1285. if (!isshowPopupEnabled) return;
  1286. if (enableP === 0) return;
  1287.  
  1288. ishidePopupEnabled = true;
  1289.  
  1290. popup.src = src;
  1291. popup.style.display = 'block';
  1292. popupContainer.style.display = 'block';
  1293. popupContainer.style.position = 'fixed';
  1294. popupContainer.style.transform = 'translate(-50%, -50%) scaleX(1) scaleY(1)';
  1295. backdrop.style.display = 'block';
  1296. backdrop.style.zIndex = '999';
  1297. backdrop.style.backdropFilter = 'blur(10px)';
  1298.  
  1299. if (positionP === 'center') {
  1300. popupContainer.style.top = '50%';
  1301. popupContainer.style.left = '50%';
  1302. } else {
  1303. popupContainer.style.top = `${mouseY}px`;
  1304. popupContainer.style.left = `${mouseX}px`;
  1305. }
  1306.  
  1307. let rectI = popup.getBoundingClientRect();
  1308. rectIH = rectI.height;
  1309. rectIW = rectI.width;
  1310. rectIT = rectI.top;
  1311. rectIL = rectI.left;
  1312.  
  1313. var img = new Image();
  1314. img.onload = function() {
  1315. natHeight = img.naturalHeight;
  1316. natWidth = img.naturalWidth;
  1317. };
  1318. img.src = popup.src
  1319.  
  1320. natXratio = natWidth / rectIW;
  1321. natYratio = natHeight / rectIH;
  1322. rectIRatio = rectIH / rectIW;
  1323.  
  1324. if (positionP === '') {
  1325. if (mouseY < window.innerHeight * 0.33) {
  1326. popupContainer.style.top = mouseY + (rectIH / 2) - 40 + 'px';
  1327. } else if (mouseY >= window.innerHeight * 0.33 && mouseY < window.innerHeight * 0.67) {
  1328. popupContainer.style.top = mouseY + 'px';
  1329. } else if (mouseY >= window.innerHeight * 0.67) {
  1330. popupContainer.style.top = mouseY - (rectIH / 2) + 40 + 'px';
  1331. }
  1332. if (mouseX < window.innerWidth * 0.33) {
  1333. popupContainer.style.left = mouseX + (rectIW / 2) - 40 + 'px';
  1334. } else if (mouseX >= window.innerWidth * 0.33 && mouseX < window.innerWidth * 0.67) {
  1335. popupContainer.style.left = mouseX + 'px';
  1336. } else if (mouseX >= window.innerWidth * 0.67) {
  1337. popupContainer.style.left = mouseX - (rectIW / 2) + 40 + 'px';
  1338. }
  1339. }
  1340. if (isAlbum) {
  1341. LockedXMode();
  1342. }
  1343.  
  1344. }
  1345.  
  1346. let isAlbum;
  1347. let currentContainer;
  1348. document.addEventListener('mouseover', function(e) {
  1349. if (popupTimer) return;
  1350.  
  1351. let target = e.target.closest(imgContainers);
  1352. let relatedTarget = event.relatedTarget;
  1353.  
  1354. if (target.querySelector(nopeElements)) return;
  1355. currentContainer = target;
  1356. const imageElement = currentContainer.querySelector(imgElements);
  1357. specialElements.forEach(pair => {
  1358. if (imageElement.matches(pair.selector)) {
  1359. pair.func(imageElement);
  1360. }
  1361. });
  1362. if (imageElement) {
  1363. currentZeroImgElement = imageElement;
  1364. if (currentZeroImgElement) {
  1365. let pair = albumSelector.find(pair => currentZeroImgElement.matches(pair.imgElement));
  1366. if (pair) {
  1367. let closestAlbumElement = currentZeroImgElement.closest(pair.albumElements);
  1368. isAlbum = closestAlbumElement && closestAlbumElement.querySelectorAll(pair.imgElement).length > 1;
  1369. if (isAlbum) {
  1370. imgElementsList = Array.from(closestAlbumElement.querySelectorAll(pair.imgElement));
  1371. let zeroIndex = imgElementsList.indexOf(currentZeroImgElement);
  1372. TR.textContent = `${zeroIndex + 1}/${imgElementsList.length}`;
  1373. if (zeroIndex === 0) {
  1374. LeftBar.style.display = 'none';
  1375. } else {
  1376. LeftBar.style.display = '';
  1377. }
  1378. if (zeroIndex === imgElementsList.length - 1) {
  1379. RightBar.style.display = 'none';
  1380. } else {
  1381. RightBar.style.display = '';
  1382. }
  1383. }
  1384. } else {
  1385. isAlbum = false;
  1386. TR.textContent = '';
  1387. LeftBar.style.display = '';
  1388. RightBar.style.display = '';
  1389. }
  1390. }
  1391. if (intervalP === '') {
  1392. showPopup(imageElement.src, e.clientX, e.clientY);
  1393. } else {
  1394. popupTimer = setTimeout(() => {
  1395. showPopup(imageElement.src, e.clientX, e.clientY);
  1396. popupTimer = null;
  1397. }, parseInt(intervalP));
  1398. }
  1399. }
  1400. });
  1401. //-------------------------------------------------------------------------
  1402.  
  1403. // hide popup
  1404. function hidePopup() {
  1405.  
  1406. isshowPopupEnabled = true;
  1407. if (!ishidePopupEnabled) return;
  1408. imgElementsList = [];
  1409. if (popupTimer) {
  1410. clearTimeout(popupTimer);
  1411. }
  1412. popup.style.display = 'none';
  1413. popupContainer.style.display = 'none';
  1414.  
  1415. NoMode();
  1416. popup.style.maxHeight = 'calc(90vh - 10px)';
  1417. popup.style.width = '';
  1418. popup.style.height = '';
  1419. popupContainer.style.left = '50%';
  1420. popupContainer.style.top = '50%';
  1421. popupContainer.style.position = 'fixed';
  1422. popupContainer.style.transform = 'translate(-50%, -50%) scaleX(1) scaleY(1)';
  1423. popupContainer.style.transformOrigin = '';
  1424. popupContainer.style.outline = '';
  1425. backdrop.style.zIndex = '';
  1426. backdrop.style.display = 'none';
  1427. backdrop.style.backdropFilter = '';
  1428. LeftBar.style.opacity = '0';
  1429. RightBar.style.opacity = '0';
  1430. style2.innerHTML = `
  1431. .LeftBar::before,
  1432. .LeftBar::after,
  1433. .RighttBar::before,
  1434. .RighttBar::after {
  1435. display: none;
  1436. }`;
  1437. document.querySelectorAll('.specialElement').forEach(e => e.remove());
  1438.  
  1439. clickCount = 0;
  1440. cropAreaSet = false;
  1441. CropCover.style.zIndex = '1';
  1442. CropCover.style.border = '';
  1443. document.querySelectorAll('.cropArea1').forEach(e => e.remove())
  1444. document.querySelectorAll('.cropArea2').forEach(e => e.remove())
  1445. document.querySelectorAll('.cropArea3').forEach(e => e.remove())
  1446. document.querySelectorAll('.cropArea4').forEach(e => e.remove())
  1447.  
  1448. var removecropCanvas = document.getElementById('cropCanvas');
  1449. removecropCanvas.remove();
  1450. CropCover.remove();
  1451.  
  1452. }
  1453.  
  1454. popupContainer.addEventListener('mouseout', function(event) {
  1455. let relatedTarget = event.relatedTarget;
  1456. if (relatedTarget && (popupContainer.contains(relatedTarget) || relatedTarget.matches('.imgContainers'))) {
  1457. return;
  1458. }
  1459.  
  1460. document.querySelectorAll('.specialElement').forEach(e => e.remove());
  1461. hidePopup();
  1462.  
  1463. if (intervalP !== '') {
  1464. popupTimer = setTimeout(() => {
  1465. popupTimer = null;
  1466. }, parseInt(intervalP));
  1467. }
  1468. });
  1469.  
  1470. //-------------------------------------------------------------------------
  1471.  
  1472.  
  1473. // lock popup in screen
  1474. backdrop.addEventListener('dblclick', function(event) {
  1475. clearTimeout(clickTimeout);
  1476. ishidePopupEnabled = true;
  1477. hidePopup();
  1478. });
  1479. backdrop.addEventListener('click', function(event) {
  1480. if (clickTimeout) clearTimeout(clickTimeout);
  1481. clickTimeout = setTimeout(function() {
  1482. ResetGeometry();
  1483. if (isScale) {
  1484. isScale = false;
  1485. isScaleTL = false;
  1486. isScaleTR = false;
  1487. isScaleBL = false;
  1488. isScaleBR = false;
  1489. ScalingMode0();
  1490.  
  1491. } else if (isLockedY) {
  1492. isLockedY = false;
  1493. popupContainer.style.border = '';
  1494. } else {
  1495. backdrop.style.zIndex = '';
  1496. backdrop.style.display = 'none';
  1497. backdrop.style.backdropFilter = '';
  1498. isshowPopupEnabled = false;
  1499. }
  1500. }, 300);
  1501. });
  1502.  
  1503. }
  1504. //-------------------------------------------------------------------------
  1505.  
  1506.  
  1507. // Is to be run -----------------------------------------------------------
  1508. if (URLmatched) {
  1509. const indicatorBar = document.createElement('div');
  1510. indicatorBar.style.cssText = `
  1511. position: fixed;
  1512. bottom: 0;
  1513. left: 50%;
  1514. transform: translateX(-50%);
  1515. z-index: 9999;
  1516. height: 30px;
  1517. width: 50vw;
  1518. background: #0000;`;
  1519. document.body.appendChild(indicatorBar);
  1520.  
  1521. function toggleIndicator() {
  1522. enableP = 1 - enableP;
  1523.  
  1524. indicatorBar.style.background = enableP ? 'linear-gradient(to right, rgba(50, 190, 152, 0) 0%, rgba(50, 190, 152, 0.5) 25%, rgba(50, 190, 152, 0.5) 75%, rgba(50, 190, 152, 0) 100%)' : 'linear-gradient(to right, rgba(174, 0, 1, 0) 0%, rgba(174, 0, 1, 0.5) 25%, rgba(174, 0, 1, 0.5) 75%, rgba(174, 0, 1, 0) 100%)';
  1525. setTimeout(() => {
  1526. indicatorBar.style.background = '#0000';
  1527. }, 1000);
  1528. if (enableP === 1) {
  1529. HoverZoomMinus();
  1530. } else {
  1531. document.querySelectorAll('.popup-container').forEach(e => e.remove());
  1532. document.querySelectorAll('.popup-backdrop').forEach(e => e.remove());
  1533. document.querySelectorAll('.specialElement').forEach(e => e.remove());
  1534. }
  1535. }
  1536. let hoverTimeout;
  1537. indicatorBar.addEventListener('mouseenter', () => {
  1538. hoverTimeout = setTimeout(toggleIndicator, 500);
  1539. });
  1540. indicatorBar.addEventListener('mouseleave', () => {
  1541. clearTimeout(hoverTimeout);
  1542. indicatorBar.style.background = '#0000';
  1543. });
  1544. if (enableP === 1) {
  1545. HoverZoomMinus();
  1546. }
  1547. } else {
  1548. return;
  1549. }
  1550.  
  1551. })();