Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

image-focus.js 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. /* Image-focus.js http://share.obormot.net/misc/gwern/image-focus.js */
  2. /* Written by Obormot, 15 February 2019 */
  3. /* Lightweight dependency-free JavaScript library for "click to focus/zoom" images in HTML web pages. Originally coded for Obormot.net / GreaterWrong.com. */
  4. if (typeof window.GW == "undefined")
  5. window.GW = { };
  6. GW.temp = { };
  7. GW.isMobile = ('ontouchstart' in document.documentElement);
  8. /********************/
  9. /* DEBUGGING OUTPUT */
  10. /********************/
  11. function GWLog (string) {
  12. if (GW.loggingEnabled || localStorage.getItem("logging-enabled") == "true")
  13. console.log(string);
  14. }
  15. GW.enableLogging = (permanently = false) => {
  16. if (permanently)
  17. localStorage.setItem("logging-enabled", "true");
  18. else
  19. GW.loggingEnabled = true;
  20. };
  21. GW.disableLogging = (permanently = false) => {
  22. if (permanently)
  23. localStorage.removeItem("logging-enabled");
  24. else
  25. GW.loggingEnabled = false;
  26. };
  27. /****************/
  28. /* MISC HELPERS */
  29. /****************/
  30. /* Given an HTML string, creates an element from that HTML, adds it to
  31. #ui-elements-container (creating the latter if it does not exist), and
  32. returns the created element.
  33. */
  34. function addUIElement(element_html) {
  35. var ui_elements_container = document.querySelector("#ui-elements-container");
  36. if (!ui_elements_container) {
  37. ui_elements_container = document.createElement("div");
  38. ui_elements_container.id = "ui-elements-container";
  39. document.querySelector("body").appendChild(ui_elements_container);
  40. }
  41. ui_elements_container.insertAdjacentHTML("beforeend", element_html);
  42. return ui_elements_container.lastElementChild;
  43. }
  44. /* Toggles whether the page is scrollable.
  45. */
  46. function togglePageScrolling(enable) {
  47. if (!enable) {
  48. window.addEventListener('keydown', GW.imageFocus.keyDown = (event) => {
  49. let forbiddenKeys = [ " ", "Spacebar", "ArrowUp", "ArrowDown", "Up", "Down" ];
  50. if (forbiddenKeys.contains(event.key) &&
  51. event.target == document.body) {
  52. event.preventDefault();
  53. }
  54. });
  55. } else {
  56. window.removeEventListener('keydown', GW.imageFocus.keyDown);
  57. }
  58. }
  59. /* Returns true if the array contains the given element.
  60. */
  61. Array.prototype.contains = function (element) {
  62. return (this.indexOf(element) !== -1);
  63. }
  64. /* Returns true if the string begins with the given prefix.
  65. */
  66. String.prototype.hasPrefix = function (prefix) {
  67. return (this.lastIndexOf(prefix, 0) === 0);
  68. }
  69. /***************/
  70. /* IMAGE FOCUS */
  71. /***************/
  72. function imageFocusSetup() {
  73. if (typeof GW.imageFocus == "undefined")
  74. GW.imageFocus = {
  75. contentImagesSelector: ".gallery img",
  76. focusedImageSelector: ".gallery img.focused",
  77. shrinkRatio: 0.975,
  78. hideUITimerDuration: 1500,
  79. hideUITimerExpired: () => {
  80. GWLog("GW.imageFocus.hideUITimerExpired");
  81. let currentTime = new Date();
  82. let timeSinceLastMouseMove = (new Date()) - GW.imageFocus.mouseLastMovedAt;
  83. if (timeSinceLastMouseMove < GW.imageFocus.hideUITimerDuration) {
  84. GW.imageFocus.hideUITimer = setTimeout(GW.imageFocus.hideUITimerExpired, (GW.imageFocus.hideUITimerDuration - timeSinceLastMouseMove));
  85. } else {
  86. hideImageFocusUI();
  87. cancelImageFocusHideUITimer();
  88. }
  89. }
  90. };
  91. GWLog("imageFocusSetup");
  92. // Create event listener for clicking on images to focus them.
  93. GW.imageClickedToFocus = (event) => {
  94. GWLog("GW.imageClickedToFocus");
  95. focusImage(event.target);
  96. if (!GW.isMobile) {
  97. // Set timer to hide the image focus UI.
  98. unhideImageFocusUI();
  99. GW.imageFocus.hideUITimer = setTimeout(GW.imageFocus.hideUITimerExpired, GW.imageFocus.hideUITimerDuration);
  100. }
  101. };
  102. // Add the listener to all content images.
  103. document.querySelectorAll(GW.imageFocus.contentImagesSelector).forEach(image => {
  104. image.addEventListener("click", GW.imageClickedToFocus);
  105. });
  106. // Add image wrapper class.
  107. document.querySelectorAll(GW.imageFocus.contentImagesSelector).forEach(image => {
  108. let wrapper = image.closest(".img");
  109. if (wrapper)
  110. wrapper.classList.add("image-wrapper");
  111. });
  112. // Create the image focus overlay.
  113. let imageFocusOverlay = addUIElement("<div id='image-focus-overlay'>" +
  114. `<div class='help-overlay'>
  115. <p><strong>Arrow keys:</strong> Next/previous image</p>
  116. <p><strong>Escape</strong> or <strong>click</strong>: Hide zoomed image</p>
  117. <p><strong>Space bar:</strong> Reset image size & position</p>
  118. <p><strong>Scroll</strong> to zoom in/out</p>
  119. <p>(When zoomed in, <strong>drag</strong> to pan; <br/><strong>double-click</strong> to close)</p>
  120. </div>
  121. <div class='image-number'></div>
  122. <div class='slideshow-buttons'>
  123. <button type='button' class='slideshow-button previous' tabindex='-1' title='Previous image'>
  124. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
  125. <path d="M34.52 239.03L228.87 44.69c9.37-9.37 24.57-9.37 33.94 0l22.67 22.67c9.36 9.36 9.37 24.52.04 33.9L131.49 256l154.02 154.75c9.34 9.38 9.32 24.54-.04 33.9l-22.67 22.67c-9.37 9.37-24.57 9.37-33.94 0L34.52 272.97c-9.37-9.37-9.37-24.57 0-33.94z"/>
  126. </svg>
  127. </button>
  128. <button type='button' class='slideshow-button next' tabindex='-1' title='Next image'>
  129. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
  130. <path d="M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z"/>
  131. </svg>
  132. </button>
  133. </div>
  134. <div class='caption'></div>` +
  135. "</div>");
  136. imageFocusOverlay.dropShadowFilterForImages = " drop-shadow(10px 10px 10px #000) drop-shadow(0 0 10px #444)";
  137. // On orientation change, reset the size & position.
  138. window.matchMedia('(orientation: portrait)').addListener(() => { setTimeout(resetFocusedImagePosition, 0); });
  139. // Accesskey-L starts the slideshow.
  140. (document.querySelector(GW.imageFocus.contentImagesSelector)||{}).accessKey = 'l';
  141. // Count how many images there are in the post, and set the "… of X" label to that.
  142. ((document.querySelector("#image-focus-overlay .image-number")||{}).dataset||{}).numberOfImages = document.querySelectorAll(GW.imageFocus.contentImagesSelector).length;
  143. // Activate the buttons.
  144. imageFocusOverlay.querySelectorAll(".slideshow-button").forEach(button => {
  145. button.addEventListener("click", GW.imageFocus.slideshowButtonClicked = (event) => {
  146. GWLog("GW.imageFocus.slideshowButtonClicked");
  147. focusNextImage(event.target.classList.contains("next"));
  148. event.target.blur();
  149. });
  150. });
  151. // UI starts out hidden.
  152. hideImageFocusUI();
  153. }
  154. function focusImage(imageToFocus) {
  155. GWLog("focusImage");
  156. // Clear 'last-focused' class of last focused image.
  157. let lastFocusedImage = document.querySelector("img.last-focused");
  158. if (lastFocusedImage) {
  159. lastFocusedImage.classList.remove("last-focused");
  160. lastFocusedImage.removeAttribute("accesskey");
  161. }
  162. // Create the focused version of the image.
  163. imageToFocus.classList.toggle("focused", true);
  164. let imageFocusOverlay = document.querySelector("#image-focus-overlay");
  165. let clonedImage = imageToFocus.cloneNode(true);
  166. clonedImage.style = "";
  167. clonedImage.removeAttribute("width");
  168. clonedImage.removeAttribute("height");
  169. clonedImage.style.filter = imageToFocus.style.filter + imageFocusOverlay.dropShadowFilterForImages;
  170. // Add the image to the overlay.
  171. imageFocusOverlay.appendChild(clonedImage);
  172. imageFocusOverlay.classList.toggle("engaged", true);
  173. // Set image to default size and position.
  174. resetFocusedImagePosition();
  175. // Add listener to zoom image with scroll wheel.
  176. window.addEventListener("wheel", GW.imageFocus.scrollEvent = (event) => {
  177. GWLog("GW.imageFocus.scrollEvent");
  178. event.preventDefault();
  179. let image = document.querySelector("#image-focus-overlay img.focused");
  180. // Remove the filter.
  181. image.savedFilter = image.style.filter;
  182. image.style.filter = 'none';
  183. // Locate point under cursor.
  184. let imageBoundingBox = image.getBoundingClientRect();
  185. // Calculate resize factor.
  186. var factor = (image.height > 10 && image.width > 10) || event.deltaY < 0 ?
  187. 1 + Math.sqrt(Math.abs(event.deltaY))/100.0 :
  188. 1;
  189. // Resize.
  190. image.style.width = (event.deltaY < 0 ?
  191. (image.clientWidth * factor) :
  192. (image.clientWidth / factor))
  193. + "px";
  194. image.style.height = "";
  195. // Designate zoom origin.
  196. var zoomOrigin;
  197. // Zoom from cursor if we're zoomed in to where image exceeds screen, AND
  198. // the cursor is over the image.
  199. let imageSizeExceedsWindowBounds = (image.getBoundingClientRect().width > window.innerWidth || image.getBoundingClientRect().height > window.innerHeight);
  200. let zoomingFromCursor = imageSizeExceedsWindowBounds &&
  201. (imageBoundingBox.left <= event.clientX &&
  202. event.clientX <= imageBoundingBox.right &&
  203. imageBoundingBox.top <= event.clientY &&
  204. event.clientY <= imageBoundingBox.bottom);
  205. // Otherwise, if we're zooming OUT, zoom from window center; if we're
  206. // zooming IN, zoom from image center.
  207. let zoomingFromWindowCenter = event.deltaY > 0;
  208. if (zoomingFromCursor)
  209. zoomOrigin = { x: event.clientX,
  210. y: event.clientY };
  211. else if (zoomingFromWindowCenter)
  212. zoomOrigin = { x: window.innerWidth / 2,
  213. y: window.innerHeight / 2 };
  214. else
  215. zoomOrigin = { x: imageBoundingBox.x + imageBoundingBox.width / 2,
  216. y: imageBoundingBox.y + imageBoundingBox.height / 2 };
  217. // Calculate offset from zoom origin.
  218. let offsetOfImageFromZoomOrigin = {
  219. x: imageBoundingBox.x - zoomOrigin.x,
  220. y: imageBoundingBox.y - zoomOrigin.y
  221. }
  222. // Calculate delta from centered zoom.
  223. let deltaFromCenteredZoom = {
  224. x: image.getBoundingClientRect().x - (zoomOrigin.x + (event.deltaY < 0 ? offsetOfImageFromZoomOrigin.x * factor : offsetOfImageFromZoomOrigin.x / factor)),
  225. y: image.getBoundingClientRect().y - (zoomOrigin.y + (event.deltaY < 0 ? offsetOfImageFromZoomOrigin.y * factor : offsetOfImageFromZoomOrigin.y / factor))
  226. }
  227. // Adjust image position appropriately.
  228. image.style.left = parseInt(getComputedStyle(image).left) - deltaFromCenteredZoom.x + "px";
  229. image.style.top = parseInt(getComputedStyle(image).top) - deltaFromCenteredZoom.y + "px";
  230. // Gradually re-center image, if it's smaller than the window.
  231. if (!imageSizeExceedsWindowBounds) {
  232. let imageCenter = { x: image.getBoundingClientRect().x + image.getBoundingClientRect().width / 2,
  233. y: image.getBoundingClientRect().y + image.getBoundingClientRect().height / 2 }
  234. let windowCenter = { x: window.innerWidth / 2,
  235. y: window.innerHeight / 2 }
  236. let imageOffsetFromCenter = { x: windowCenter.x - imageCenter.x,
  237. y: windowCenter.y - imageCenter.y }
  238. // Divide the offset by 10 because we're nudging the image toward center,
  239. // not jumping it there.
  240. image.style.left = parseInt(getComputedStyle(image).left) + imageOffsetFromCenter.x / 10 + "px";
  241. image.style.top = parseInt(getComputedStyle(image).top) + imageOffsetFromCenter.y / 10 + "px";
  242. }
  243. // Put the filter back.
  244. image.style.filter = image.savedFilter;
  245. // Set the cursor appropriately.
  246. setFocusedImageCursor();
  247. }, { passive: false });
  248. window.addEventListener("MozMousePixelScroll", GW.imageFocus.oldFirefoxCompatibilityScrollEvent = (event) => {
  249. GWLog("GW.imageFocus.oldFirefoxCompatibilityScrollEvent");
  250. event.preventDefault();
  251. });
  252. // If image is bigger than viewport, it's draggable. Otherwise, click unfocuses.
  253. window.addEventListener("mouseup", GW.imageFocus.mouseUp = (event) => {
  254. GWLog("GW.imageFocus.mouseUp");
  255. window.onmousemove = '';
  256. // We only want to do anything on left-clicks.
  257. if (event.button != 0) return;
  258. // Don't unfocus if click was on a slideshow next/prev button!
  259. if (event.target.classList.contains("slideshow-button")) return;
  260. // We also don't want to do anything if clicked on the help overlay.
  261. if (event.target.classList.contains("help-overlay") ||
  262. event.target.closest(".help-overlay"))
  263. return;
  264. let focusedImage = document.querySelector("#image-focus-overlay img.focused");
  265. if ((event.target == focusedImage || event.target.tagName == "HTML") &&
  266. (focusedImage.height >= window.innerHeight || focusedImage.width >= window.innerWidth)) {
  267. // If the mouseup event was the end of a pan of an overside image,
  268. // put the filter back; do not unfocus.
  269. focusedImage.style.filter = focusedImage.savedFilter;
  270. } else if (event.target.tagName != "HTML") {
  271. unfocusImageOverlay();
  272. return;
  273. }
  274. });
  275. window.addEventListener("mousedown", GW.imageFocus.mouseDown = (event) => {
  276. GWLog("GW.imageFocus.mouseDown");
  277. // We only want to do anything on left-clicks.
  278. if (event.button != 0) return;
  279. event.preventDefault();
  280. let focusedImage = document.querySelector("#image-focus-overlay img.focused");
  281. if (focusedImage.height >= window.innerHeight || focusedImage.width >= window.innerWidth) {
  282. let mouseCoordX = event.clientX;
  283. let mouseCoordY = event.clientY;
  284. let imageCoordX = parseInt(getComputedStyle(focusedImage).left);
  285. let imageCoordY = parseInt(getComputedStyle(focusedImage).top);
  286. // Save the filter.
  287. focusedImage.savedFilter = focusedImage.style.filter;
  288. window.onmousemove = (event) => {
  289. // Remove the filter.
  290. focusedImage.style.filter = "none";
  291. focusedImage.style.left = imageCoordX + event.clientX - mouseCoordX + 'px';
  292. focusedImage.style.top = imageCoordY + event.clientY - mouseCoordY + 'px';
  293. };
  294. return false;
  295. }
  296. });
  297. // Double-click on the image unfocuses.
  298. clonedImage.addEventListener('dblclick', GW.imageFocus.doubleClick = (event) => {
  299. GWLog("GW.imageFocus.doubleClick");
  300. if (event.target.classList.contains("slideshow-button")) return;
  301. unfocusImageOverlay();
  302. });
  303. // Escape key unfocuses, spacebar resets.
  304. document.addEventListener("keyup", GW.imageFocus.keyUp = (event) => {
  305. GWLog("GW.imageFocus.keyUp");
  306. let allowedKeys = [ " ", "Spacebar", "Escape", "Esc", "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Up", "Down", "Left", "Right" ];
  307. if (!allowedKeys.contains(event.key) ||
  308. getComputedStyle(document.querySelector("#image-focus-overlay")).display == "none") return;
  309. event.preventDefault();
  310. switch (event.key) {
  311. case "Escape":
  312. case "Esc":
  313. unfocusImageOverlay();
  314. break;
  315. case " ":
  316. case "Spacebar":
  317. resetFocusedImagePosition();
  318. break;
  319. case "ArrowDown":
  320. case "Down":
  321. case "ArrowRight":
  322. case "Right":
  323. if (document.querySelector(GW.imageFocus.focusedImageSelector)) focusNextImage(true);
  324. break;
  325. case "ArrowUp":
  326. case "Up":
  327. case "ArrowLeft":
  328. case "Left":
  329. if (document.querySelector(GW.imageFocus.focusedImageSelector)) focusNextImage(false);
  330. break;
  331. }
  332. });
  333. setTimeout(() => {
  334. // Prevent spacebar or arrow keys from scrolling page when image focused.
  335. togglePageScrolling(false);
  336. });
  337. // Mark the overlay as being in slide show mode (to show buttons/count).
  338. imageFocusOverlay.classList.add("slideshow");
  339. // Set state of next/previous buttons.
  340. let images = document.querySelectorAll(GW.imageFocus.contentImagesSelector);
  341. var indexOfFocusedImage = getIndexOfFocusedImage();
  342. imageFocusOverlay.querySelector(".slideshow-button.previous").disabled = (indexOfFocusedImage == 0);
  343. imageFocusOverlay.querySelector(".slideshow-button.next").disabled = (indexOfFocusedImage == images.length - 1);
  344. // Set the image number.
  345. document.querySelector("#image-focus-overlay .image-number").textContent = (indexOfFocusedImage + 1);
  346. // Replace the hash.
  347. history.replaceState(null, null, "#if_slide_" + (indexOfFocusedImage + 1));
  348. // Set the caption.
  349. setImageFocusCaption();
  350. // Moving mouse unhides image focus UI.
  351. window.addEventListener("mousemove", GW.imageFocus.mouseMoved = (event) => {
  352. GWLog("GW.imageFocus.mouseMoved");
  353. let currentDateTime = new Date();
  354. if (!(event.target.tagName == "IMG" || event.target.id == "image-focus-overlay")) {
  355. cancelImageFocusHideUITimer();
  356. } else {
  357. if (!GW.imageFocus.hideUITimer) {
  358. unhideImageFocusUI();
  359. GW.imageFocus.hideUITimer = setTimeout(GW.imageFocus.hideUITimerExpired, GW.imageFocus.hideUITimerDuration);
  360. }
  361. GW.imageFocus.mouseLastMovedAt = currentDateTime;
  362. }
  363. });
  364. }
  365. function resetFocusedImagePosition() {
  366. GWLog("resetFocusedImagePosition");
  367. let focusedImage = document.querySelector("#image-focus-overlay img.focused");
  368. if (!focusedImage) return;
  369. let sourceImage = document.querySelector(GW.imageFocus.focusedImageSelector);
  370. // Make sure that initially, the image fits into the viewport.
  371. let constrainedWidth = Math.min(sourceImage.naturalWidth, window.innerWidth * GW.imageFocus.shrinkRatio);
  372. let widthShrinkRatio = constrainedWidth / sourceImage.naturalWidth;
  373. var constrainedHeight = Math.min(sourceImage.naturalHeight, window.innerHeight * GW.imageFocus.shrinkRatio);
  374. let heightShrinkRatio = constrainedHeight / sourceImage.naturalHeight;
  375. let shrinkRatio = Math.min(widthShrinkRatio, heightShrinkRatio);
  376. focusedImage.style.width = (sourceImage.naturalWidth * shrinkRatio) + "px";
  377. focusedImage.style.height = (sourceImage.naturalHeight * shrinkRatio) + "px";
  378. // Remove modifications to position.
  379. focusedImage.style.left = "";
  380. focusedImage.style.top = "";
  381. // Set the cursor appropriately.
  382. setFocusedImageCursor();
  383. }
  384. function setFocusedImageCursor() {
  385. let focusedImage = document.querySelector("#image-focus-overlay img.focused");
  386. if (!focusedImage) return;
  387. focusedImage.style.cursor = (focusedImage.height >= window.innerHeight || focusedImage.width >= window.innerWidth) ?
  388. 'move' : '';
  389. }
  390. function unfocusImageOverlay() {
  391. GWLog("unfocusImageOverlay");
  392. // Remove event listeners.
  393. window.removeEventListener("wheel", GW.imageFocus.scrollEvent);
  394. window.removeEventListener("MozMousePixelScroll", GW.imageFocus.oldFirefoxCompatibilityScrollEvent);
  395. // NOTE: The double-click listener does not need to be removed manually,
  396. // because the focused (cloned) image will be removed anyway.
  397. document.removeEventListener("keyup", GW.imageFocus.keyUp);
  398. window.removeEventListener("mousemove", GW.imageFocus.mouseMoved);
  399. window.removeEventListener("mousedown", GW.imageFocus.mouseDown);
  400. window.removeEventListener("mouseup", GW.imageFocus.mouseUp);
  401. // Set accesskey of currently focused image.
  402. let currentlyFocusedImage = document.querySelector(GW.imageFocus.focusedImageSelector)
  403. if (currentlyFocusedImage) {
  404. currentlyFocusedImage.classList.toggle("last-focused", true);
  405. currentlyFocusedImage.accessKey = 'l';
  406. }
  407. // Remove focused image and hide overlay.
  408. let imageFocusOverlay = document.querySelector("#image-focus-overlay");
  409. imageFocusOverlay.classList.remove("engaged");
  410. imageFocusOverlay.querySelector("img.focused").remove();
  411. // Unset "focused" class of focused image.
  412. document.querySelector(GW.imageFocus.focusedImageSelector).classList.remove("focused");
  413. setTimeout(() => {
  414. // Re-enable page scrolling.
  415. togglePageScrolling(true);
  416. });
  417. // Reset the hash, if needed.
  418. if (location.hash.hasPrefix("#if_slide_"))
  419. history.replaceState(null, null, "#");
  420. }
  421. function getIndexOfFocusedImage() {
  422. let images = document.querySelectorAll(GW.imageFocus.contentImagesSelector);
  423. var indexOfFocusedImage = -1;
  424. for (i = 0; i < images.length; i++) {
  425. if (images[i].classList.contains("focused")) {
  426. indexOfFocusedImage = i;
  427. break;
  428. }
  429. }
  430. return indexOfFocusedImage;
  431. }
  432. function focusNextImage(next = true) {
  433. GWLog("focusNextImage");
  434. let images = document.querySelectorAll(GW.imageFocus.contentImagesSelector);
  435. var indexOfFocusedImage = getIndexOfFocusedImage();
  436. if (next ? (++indexOfFocusedImage == images.length) : (--indexOfFocusedImage == -1)) return;
  437. // Remove existing image.
  438. document.querySelector("#image-focus-overlay img.focused").remove();
  439. // Unset "focused" class of just-removed image.
  440. document.querySelector(GW.imageFocus.focusedImageSelector).classList.remove("focused");
  441. // Create the focused version of the image.
  442. images[indexOfFocusedImage].classList.toggle("focused", true);
  443. let imageFocusOverlay = document.querySelector("#image-focus-overlay");
  444. let clonedImage = images[indexOfFocusedImage].cloneNode(true);
  445. clonedImage.style = "";
  446. clonedImage.removeAttribute("width");
  447. clonedImage.removeAttribute("height");
  448. clonedImage.style.filter = images[indexOfFocusedImage].style.filter + imageFocusOverlay.dropShadowFilterForImages;
  449. imageFocusOverlay.appendChild(clonedImage);
  450. imageFocusOverlay.classList.toggle("engaged", true);
  451. // Set image to default size and position.
  452. resetFocusedImagePosition();
  453. // Set state of next/previous buttons.
  454. imageFocusOverlay.querySelector(".slideshow-button.previous").disabled = (indexOfFocusedImage == 0);
  455. imageFocusOverlay.querySelector(".slideshow-button.next").disabled = (indexOfFocusedImage == images.length - 1);
  456. // Set the image number display.
  457. document.querySelector("#image-focus-overlay .image-number").textContent = (indexOfFocusedImage + 1);
  458. // Set the caption.
  459. setImageFocusCaption();
  460. // Replace the hash.
  461. history.replaceState(null, null, "#if_slide_" + (indexOfFocusedImage + 1));
  462. }
  463. function setImageFocusCaption() {
  464. GWLog("setImageFocusCaption");
  465. var T = { }; // Temporary storage.
  466. // Clear existing caption, if any.
  467. let captionContainer = document.querySelector("#image-focus-overlay .caption");
  468. Array.from(captionContainer.children).forEach(child => { child.remove(); });
  469. // Determine caption.
  470. let currentlyFocusedImage = document.querySelector(GW.imageFocus.focusedImageSelector);
  471. var captionHTML;
  472. if ((T.enclosingFigure = currentlyFocusedImage.closest("figure")) &&
  473. (T.figcaption = T.enclosingFigure.querySelector("figcaption"))) {
  474. captionHTML = (T.figcaption.querySelector("p")) ?
  475. T.figcaption.innerHTML :
  476. "<p>" + T.figcaption.innerHTML + "</p>";
  477. } else if (currentlyFocusedImage.title != "") {
  478. captionHTML = `<p>${currentlyFocusedImage.title}</p>`;
  479. }
  480. // Insert the caption, if any.
  481. if (captionHTML) captionContainer.insertAdjacentHTML("beforeend", captionHTML);
  482. }
  483. function hideImageFocusUI() {
  484. GWLog("hideImageFocusUI");
  485. let imageFocusOverlay = document.querySelector("#image-focus-overlay");
  486. imageFocusOverlay.querySelectorAll(".slideshow-button, .help-overlay, .image-number, .caption").forEach(element => {
  487. element.classList.toggle("hidden", true);
  488. });
  489. }
  490. function unhideImageFocusUI() {
  491. GWLog("unhideImageFocusUI");
  492. let imageFocusOverlay = document.querySelector("#image-focus-overlay");
  493. imageFocusOverlay.querySelectorAll(".slideshow-button, .help-overlay, .image-number, .caption").forEach(element => {
  494. element.classList.remove("hidden");
  495. });
  496. }
  497. function cancelImageFocusHideUITimer() {
  498. clearTimeout(GW.imageFocus.hideUITimer);
  499. GW.imageFocus.hideUITimer = null;
  500. }
  501. function focusImageSpecifiedByURL() {
  502. GWLog("focusImageSpecifiedByURL");
  503. if (location.hash.hasPrefix("#if_slide_")) {
  504. document.addEventListener("readystatechange", () => {
  505. let images = document.querySelectorAll(GW.imageFocus.contentImagesSelector);
  506. let imageToFocus = (/#if_slide_([0-9]+)/.exec(location.hash)||{})[1];
  507. if (imageToFocus > 0 && imageToFocus <= images.length) {
  508. focusImage(images[imageToFocus - 1]);
  509. if (!GW.isMobile) {
  510. // Set timer to hide the image focus UI.
  511. unhideImageFocusUI();
  512. GW.imageFocus.hideUITimer = setTimeout(GW.imageFocus.hideUITimerExpired, GW.imageFocus.hideUITimerDuration);
  513. }
  514. }
  515. });
  516. }
  517. }