Hover Zoom Minus --

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

当前为 2024-04-02 提交的版本,查看 最新版本

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