1/** 2 * Image link preview functionality 3 */ 4$(document).ready(function() { 5 var previewBox = null; 6 var imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'ico']; 7 var currentPreviewSrc = null; 8 var currentLink = null; 9 var imageCache = {}; 10 var hoverTimer = null; 11 var HOVER_DELAY = 120; 12 13 function createPreviewBox() { 14 if (!previewBox) { 15 previewBox = document.createElement('div'); 16 previewBox.className = 'image-preview'; 17 document.body.appendChild(previewBox); 18 } 19 return previewBox; 20 } 21 22 function isImageLink(href) { 23 if (!href) return false; 24 var ext = href.split('.').pop().toLowerCase().split('?')[0]; 25 return imageExtensions.indexOf(ext) !== -1; 26 } 27 28 function preloadImage(href) { 29 if (imageCache[href]) return imageCache[href]; 30 31 var promise = new Promise(function(resolve, reject) { 32 var tempImg = new Image(); 33 tempImg.onload = function() { 34 imageCache[href] = { loaded: true, width: tempImg.naturalWidth, height: tempImg.naturalHeight }; 35 resolve(imageCache[href]); 36 }; 37 tempImg.onerror = function() { 38 imageCache[href] = { loaded: false, error: true }; 39 reject(imageCache[href]); 40 }; 41 tempImg.src = href; 42 }); 43 44 imageCache[href] = promise; 45 return promise; 46 } 47 48 /* ç«ç¹å¨ â¥600px ç¨ body { zoom: 1.2 } æ¾å¤§çå¼ãgetBoundingClientRect 读å°ç 49 æ¯å¸å±åç´ ï¼èåå style çå¼è¿ä¼å被æ¾å¤§ä¸æ¬¡ï¼ä¸¤å¥å使··ç¨ä¼è®© clamp 失æã 50 é¢è§å¡å²åºè§å£ãè¿éç¨ä¸æ ¹æ¢é宿µæ¢ç®æ¯ä¾ï¼å ¨é¨å å½å°è§è§åç´ åæä½ 51 ï¼ä¸ç«å hoverquote æ¬æµ®å¡çåä¸å¥åæ³ï¼ã */ 52 var zoomCache = null; 53 function zoomMetrics() { 54 if (zoomCache) return zoomCache; 55 var z = parseFloat(window.getComputedStyle(document.body).zoom) || 1; 56 var k = 1; 57 if (z !== 1) { 58 var probe = document.createElement('i'); 59 probe.style.cssText = 'position:fixed;left:0;top:0;width:0;height:0;visibility:hidden;pointer-events:none;'; 60 document.body.appendChild(probe); 61 var zero = probe.getBoundingClientRect().left; 62 probe.style.left = '100px'; 63 var readScale = (probe.getBoundingClientRect().left - zero) / 100; 64 probe.remove(); 65 if (readScale > 0) k = z / readScale; 66 } 67 zoomCache = { z: z, k: k }; 68 return zoomCache; 69 } 70 71 /* é¢è§å°ºå¯¸æãå¾çæ¯ä¾ + è§å£ãç®ï¼ä¸ååæ» 300Ã300ï¼åºå®ä¸éä¼æé¿æªå¾ 72 åæçªæ¡ï¼åä¸å¼ å¾å¨ä¸åè§å£ä¸ä¹ä¼å¿½å¤§å¿½å°ã */ 73 var PREVIEW_MAX_W = 540; 74 function sizePreview(img, href) { 75 var meta = imageCache[href]; 76 var nw = (meta && meta.width) || img.naturalWidth || 0; 77 var nh = (meta && meta.height) || img.naturalHeight || 0; 78 if (!nw || !nh) return; 79 80 var m = zoomMetrics(); 81 var viewW = document.documentElement.clientWidth || window.innerWidth; 82 var viewH = document.documentElement.clientHeight || window.innerHeight; 83 var pad = 16; // å¡çå·¦å³å è¾¹è·å计 84 var maxW = Math.max(120, Math.min(PREVIEW_MAX_W, viewW * 0.92) - pad); 85 var maxH = Math.max(120, viewH * 0.8 - pad); 86 87 var w = maxW; 88 var h = w * nh / nw; 89 if (h > maxH) { 90 h = maxH; 91 w = h * nw / nh; 92 } 93 img.style.width = (w / m.z) + 'px'; 94 img.style.height = 'auto'; 95 } 96 97 /* æä½ï¼å®½åº¦ç±å¾çæ¯ä¾å³å®ï¼ä¸è½åç¨åºå®å¼ç®è¾¹çï¼å¦å宽å¾ä¼è¢«æ½åºè§å£ã */ 98 function placePreview(link) { 99 if (!previewBox || !link) return; 100 var m = zoomMetrics(); 101 var box = previewBox.getBoundingClientRect(); 102 var boxW = box.width * m.k; 103 var boxH = box.height * m.k; 104 if (!boxW || !boxH) return; 105 106 // å¯ç¨èå´åå¯è§åºï¼clientWidth/clientHeightï¼ï¼ä¸å«æ»å¨æ¡ 107 var viewW = document.documentElement.clientWidth || window.innerWidth; 108 var viewH = document.documentElement.clientHeight || window.innerHeight; 109 var edge = 8; 110 var gap = 10; 111 112 var rect = link.getBoundingClientRect(); 113 var linkLeft = rect.left * m.k; 114 var linkTop = rect.top * m.k; 115 var linkW = rect.width * m.k; 116 var linkBottom = rect.bottom * m.k; 117 118 var left = linkLeft + linkW / 2 - boxW / 2; 119 left = Math.max(edge, Math.min(left, viewW - boxW - edge)); 120 121 // ä¼å åå¨é¾æ¥ä¸æ¹ï¼ä¸æ¹æ¾ä¸ä¸æè½å°ä¸æ¹
122 var top = linkTop - gap - boxH; 123 if (top < edge) top = linkBottom + gap; 124 top = Math.max(edge, Math.min(top, viewH - boxH - edge)); 125 126 previewBox.style.transform = 'none'; 127 previewBox.style.left = (left / m.z) + 'px'; 128 previewBox.style.top = (top / m.z) + 'px'; 129 } 130 131 function revealImage(img, preview, href) { 132 if (currentPreviewSrc !== href) return; 133 function show() { 134 if (currentPreviewSrc !== href) return; 135 img.style.visibility = 'visible'; 136 // èªç¶å°ºå¯¸æ¤æ¶æå¯é ï¼æè§å£éç®å°ºå¯¸ï¼åæä¸æ¬¡ä½ç½® 137 sizePreview(img, href); 138 if (currentLink) placePreview(currentLink); 139 } 140 if (img.decode && typeof img.decode === 'function') { 141 img.decode().then(show).catch(show); 142 } else { 143 show(); 144 } 145 } 146 147 function showPreview(e, link) { 148 var href = link.getAttribute('href'); 149 if (!isImageLink(href)) return; 150 151 currentPreviewSrc = href; 152 currentLink = link; 153 154 var preview = createPreviewBox(); 155 var img = preview.querySelector('img') || document.createElement('img'); 156 157 img.style.visibility = 'hidden'; 158 159 var cached = imageCache[href]; 160 var isCachedReady = cached && cached.loaded === true; 161 162 if (isCachedReady) { 163 img.src = href; 164 revealImage(img, preview, href); 165 } else { 166 img.onload = function() { 167 revealImage(img, preview, href); 168 }; 169 img.onerror = function() { 170 img.style.visibility = 'hidden'; 171 }; 172 img.src = href; 173 } 174 175 if (!preview.contains(img)) { 176 preview.appendChild(img); 177 } 178 179 preloadImage(href); 180 // å·²æèªç¶å°ºå¯¸æ¶å å®å¥½å¤§å°ï¼é¿å å¡çå 以é»è®¤å°ºå¯¸éªä¸ä¸ 181 sizePreview(img, href); 182 placePreview(link); 183 184 requestAnimationFrame(function() { 185 preview.classList.add('visible'); 186 }); 187 } 188 189 function hidePreview() { 190 if (hoverTimer) { 191 clearTimeout(hoverTimer); 192 hoverTimer = null; 193 } 194 if (previewBox) { 195 previewBox.classList.remove('visible'); 196 currentPreviewSrc = null; 197 currentLink = null; 198 var img = previewBox.querySelector('img'); 199 if (img) { 200 img.style.visibility = 'hidden'; 201 } 202 } 203 } 204 205 var links = document.querySelectorAll('.article-content a, .content a'); 206 var imageLinks = []; 207 links.forEach(function(link) { 208 if (isImageLink(link.getAttribute('href'))) { 209 imageLinks.push(link); 210 link.addEventListener('mouseenter', function(e) { 211 var self = this; 212 var evt = e; 213 if (hoverTimer) clearTimeout(hoverTimer); 214 hoverTimer = setTimeout(function() { 215 showPreview(evt, self); 216 }, HOVER_DELAY); 217 }); 218 link.addEventListener('mouseleave', hidePreview); 219 } 220 }); 221 222 // æç¹åæ¢ä¼æ¹ body zoomï¼ç©å½¢âè§è§åç´ çæ¢ç®æ¯ä¾è¦éç® 223 function resetZoom() { 224 zoomCache = null; 225 if (previewBox && currentLink && previewBox.classList.contains('visible')) { 226 var im = previewBox.querySelector('img'); 227 if (im) sizePreview(im, currentPreviewSrc); 228 placePreview(currentLink); 229 } 230 } 231 var zoomMq = window.matchMedia ? window.matchMedia('(min-width: 600px)') : null; 232 if (zoomMq) { 233 if (zoomMq.addEventListener) zoomMq.addEventListener('change', resetZoom); 234 else if (zoomMq.addListener) zoomMq.addListener(resetZoom); 235 } 236 window.addEventListener('resize', resetZoom, { passive: true }); 237 238 if ('requestIdleCallback' in window && imageLinks.length > 0) { 239 requestIdleCallback(function() { 240 imageLinks.forEach(function(link) { 241 var href = link.getAttribute('href'); 242 if (href && !imageCache[href]) { 243 preloadImage(href); 244 } 245 }); 246 }, { timeout: 3000 }); 247 } else if (imageLinks.length > 0) { 248 setTimeout(function() { 249 imageLinks.forEach(function(link) { 250 var href = link.getAttribute('href'); 251 if (href && !imageCache[href]) { 252 preloadImage(href); 253 } 254 }); 255 }, 1500); 256 } 257}); 258 259/** 260 * Sets up Justified Gallery. 261 */ 262if (!!$.prototype.justifiedGallery) { 263 var options = { 264 rowHeight: 140, 265 margins: 4, 266 lastRow: "justify" 267 }; 268 $(".article-gallery").justifiedGallery(options); 269} 270 271$(document).ready(function() { 272 273 /** 274 * Shows the responsive navigation menu on mobile. 275 */ 276 $("#header > #nav > ul > .icon").click(function() { 277 $("#header > #nav > ul").toggleClass("responsive"); 278 }); 279 280 281 /** 282 * Controls the different versions of the menu in blog post articles 283 * for Desktop, tablet and mobile. 284 */ 285 if ($(".post").length) { 286 var menu = $("#menu"); 287 var nav = $("#menu > #nav"); 288 var menuIcon = $("#menu-icon, #menu-icon-tablet"); 289 290 /**
291 * Display the menu on hi-res laptops and desktops. 292 */ 293 if ($(document).width() >= 1440) { 294 menu.css("visibility", "visible"); 295 menuIcon.addClass("active"); 296 } 297 298 /** 299 * Display the menu if the menu icon is clicked. 300 */ 301 menuIcon.click(function() { 302 if (menu.css("visibility") === "hidden") { 303 menu.css("visibility", "visible"); 304 menuIcon.addClass("active"); 305 } else { 306 menu.css("visibility", "hidden"); 307 menuIcon.removeClass("active"); 308 } 309 return false; 310 }); 311 312 /** 313 * Add a scroll listener to the menu to hide/show the navigation links. 314 */ 315 if (menu.length) { 316 $(window).on("scroll", function() { 317 var topDistance = menu.offset().top; 318 319 // hide only the navigation links on desktop 320 if (!nav.is(":visible") && topDistance < 50) { 321 nav.show(); 322 } else if (nav.is(":visible") && topDistance > 100) { 323 nav.hide(); 324 } 325 326 // on tablet, hide the navigation icon as well and show a "scroll to top 327 // icon" instead 328 if ( ! $( "#menu-icon" ).is(":visible") && topDistance < 50 ) { 329 $("#menu-icon-tablet").show(); 330 $("#top-icon-tablet").hide(); 331 } else if (! $( "#menu-icon" ).is(":visible") && topDistance > 100) { 332 $("#menu-icon-tablet").hide(); 333 $("#top-icon-tablet").show(); 334 } 335 }); 336 } 337 338 /** 339 * Show mobile navigation menu after scrolling upwards, 340 * hide it again after scrolling downwards. 341 */ 342 if ($( "#footer-post").length) { 343 var lastScrollTop = 0; 344 $(window).on("scroll", function() { 345 var topDistance = $(window).scrollTop(); 346 347 if (topDistance > lastScrollTop){ 348 // downscroll -> show menu 349 $("#footer-post").hide(); 350 } else { 351 // upscroll -> hide menu 352 $("#footer-post").show(); 353 } 354 lastScrollTop = topDistance; 355 356 // close all submenu"s on scroll 357 $("#nav-footer").hide(); 358 $("#toc-footer").hide(); 359 $("#share-footer").hide(); 360 361 }); 362 } 363 } 364});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.