vendor: 17,986 bytes, lines 1-544
1/* juxtapose - v1.2.1 - 2019-04-18 2 * Copyright (c) 2019 Alex Duner and Northwestern University Knight Lab 3 */ 4(function(document, window) { 5 var juxtapose = { 6 sliders: [], 7 OPTIMIZATION_ACCEPTED: 1, 8 OPTIMIZATION_WAS_CONSTRAINED: 2 9 }; 10 var flickr_key = "d90fc2d1f4acc584e08b8eaea5bf4d6c"; 11 var FLICKR_SIZE_PREFERENCES = ["Large", "Medium"]; 12 function Graphic(properties, slider) { 13 var self = this; 14 this.image = new Image(); 15 this.loaded = false; 16 this.image.onload = function() { 17 self.loaded = true; 18 slider._onLoaded(); 19 }; 20 this.image.src = properties.src; 21 this.image.alt = properties.alt || ""; 22 this.label = properties.label || false; 23 this.credit = properties.credit || false; 24 } 25 function FlickrGraphic(properties, slider) { 26 var self = this; 27 this.image = new Image(); 28 this.loaded = false; 29 this.image.onload = function() { 30 self.loaded = true; 31 slider._onLoaded(); 32 }; 33 this.flickrID = this.getFlickrID(properties.src); 34 this.callFlickrAPI(this.flickrID, self); 35 this.label = properties.label || false; 36 this.credit = properties.credit || false; 37 } 38 FlickrGraphic.prototype = { 39 getFlickrID: function(url) { 40 if (url.match(/flic.kr\/.+/i)) { 41 var encoded = url.split("/").slice(-1)[0]; 42 return base58Decode(encoded); 43 } 44 var idx = url.indexOf("flickr.com/photos/"); 45 var pos = idx + "flickr.com/photos/".length; 46 var photo_info = url.substr(pos); 47 if (photo_info.indexOf("/") == -1) return null; 48 if (photo_info.indexOf("/") === 0) photo_info = photo_info.substr(1); 49 id = photo_info.split("/")[1]; 50 return id; 51 }, 52 callFlickrAPI: function(id, self) { 53 var url = 54 "https://api.flickr.com/services/rest/?method=flickr.photos.getSizes" + 55 "&api_key=" + 56 flickr_key + 57 "&photo_id=" + 58 id + 59 "&format=json&nojsoncallback=1"; 60 var request = new XMLHttpRequest(); 61 request.open("GET", url, true); 62 request.onload = function() { 63 if (request.status >= 200 && request.status < 400) { 64 data = JSON.parse(request.responseText); 65 var flickr_url = self.bestFlickrUrl(data.sizes.size); 66 self.setFlickrImage(flickr_url); 67 } else { 68 console.error("There was an error getting the picture from Flickr"); 69 } 70 }; 71 request.onerror = function() { 72 console.error("There was an error getting the picture from Flickr"); 73 }; 74 request.send(); 75 }, 76 setFlickrImage: function(src) { 77 this.image.src = src; 78 }, 79 bestFlickrUrl: function(ary) { 80 var dict = {}; 81 for (var i = 0; i < ary.length; i++) { 82 dict[ary[i].label] = ary[i].source; 83 } 84 for (var j = 0; j < FLICKR_SIZE_PREFERENCES.length; j++) { 85 if (FLICKR_SIZE_PREFERENCES[j] in dict) { 86 return dict[FLICKR_SIZE_PREFERENCES[j]]; 87 } 88 } 89 return ary[0].source; 90 } 91 }; 92 function getNaturalDimensions(DOMelement) { 93 if (DOMelement.naturalWidth && DOMelement.naturalHeight) { 94 return { 95 width: DOMelement.naturalWidth, 96 height: DOMelement.naturalHeight 97 }; 98 } 99 var img = new Image(); 100 img.src = DOMelement.src; 101 return { width: img.width, height: img.height }; 102 } 103 function getImageDimensions(img) { 104 var dimensions = { 105 width: getNaturalDimensions(img).width, 106 height: getNaturalDimensions(img).height, 107 aspect: function() { 108 return this.width / this.height; 109 } 110 }; 111 return dimensions; 112 } 113 function addClass(element, c) { 114 if (element.classList) { 115 element.classList.add(c); 116 } else { 117 element.className += " " + c; 118 } 119 } 120 function removeClass(element, c) { 121 element.className = element.className 122 .replace(/(\S+)\s*/g, function(w, match) { 123 if (match === c) { 124 return ""; 125 } 126 return w; 127 }) 128 .replace(/^\s+/, ""); 129 } 130 function setText(element, text) { 131 if (document.body.textContent) { 132 element.textContent = text; 133 } else { 134 element.innerText = text; 135 } 136 } 137 function getComputedWidthAndHeight(element) { 138 if (window.getComputedStyle) { 139 return { 140 width: parseInt(getComputedStyle(element).width, 10), 141 height: parseInt(getComputedStyle(element).height, 10) 142 }; 143 } else { 144 w = 145 element.getBoundingClientRect().right - 146 element.getBoundingClientRect().left; 147 h = 148 element.getBoundingClientRect().bottom - 149 element.getBoundingClientRect().top; 150 return { width: parseInt(w, 10) || 0, height: parseInt(h, 10) || 0 }; 151 } 152 } 153 function viewport() { 154 var e = window, 155 a = "inner"; 156 if (!("innerWidth" in window)) { 157 a = "client"; 158 e = document.documentElement || document.body; 159 } 160 return { width: e[a + "Width"], height: e[a + "Height"] }; 161 } 162 function getPageX(e) { 163 var pageX; 164 if (e.pageX) { 165 pageX = e.pageX; 166 } else if (e.touches) { 167 pageX = e.touches[0].pageX; 168 } else { 169 pageX = 170 e.clientX + 171 document.body.scrollLeft + 172 document.documentElement.scrollLeft; 173 } 174 return pageX; 175 } 176 function getPageY(e) { 177 var pageY; 178 if (e.pageY) { 179 pageY = e.pageY; 180 } else if (e.touches) { 181 pageY = e.touches[0].pageY; 182 } else { 183 pageY = 184 e.clientY + 185 document.body.scrollTop + 186 document.documentElement.scrollTop; 187 } 188 return pageY; 189 } 190 function checkFlickr(url) { 191 if (url.match(/flic.kr\/.+/i)) { 192 return true; 193 } 194 var idx = url.indexOf("flickr.com/photos/"); 195 if (idx == -1) { 196 return false; 197 } else { 198 return true; 199 } 200 } 201 function base58Decode(encoded) { 202 var alphabet = "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ", 203 base = alphabet.length; 204 if (typeof encoded !== "string") { 205 throw '"base58Decode" only accepts strings.'; 206 } 207 var decoded = 0; 208 while (encoded) { 209 var alphabetPosition = alphabet.indexOf(encoded[0]); 210 if (alphabetPosition < 0) { 211 throw '"base58Decode" can\'t find "' + 212 encoded[0] + 213 '" in the alphabet: "' + 214 alphabet + 215 '"'; 216 } 217 var powerOf = encoded.length - 1; 218 decoded += alphabetPosition * Math.pow(base, powerOf); 219 encoded = encoded.substring(1); 220 } 221 return decoded.toString(); 222 } 223 function getLeftPercent(slider, input) { 224 if (typeof input === "string" || typeof input === "number") { 225 leftPercent = parseInt(input, 10); 226 } else { 227 var sliderRect = slider.getBoundingClientRect(); 228 var offset = { 229 top: 230 sliderRect.top + 231 document.body.scrollTop + 232 document.documentElement.scrollTop, 233 left: 234 sliderRect.left + 235 document.body.scrollLeft + 236 document.documentElement.scrollLeft 237 }; 238 var width = slider.offsetWidth; 239 var pageX = getPageX(input); 240 var relativeX = pageX - offset.left; 241 leftPercent = (relativeX / width) * 100; 242 } 243 return leftPercent; 244 } 245 function getTopPercent(slider, input) { 246 if (typeof input === "string" || typeof input === "number") { 247 topPercent = parseInt(input, 10); 248 } else { 249 var sliderRect = slider.getBoundingClientRect(); 250 var offset = { 251 top: 252 sliderRect.top + 253 document.body.scrollTop + 254 document.documentElement.scrollTop, 255 left: 256 sliderRect.left + 257 document.body.scrollLeft + 258 document.documentElement.scrollLeft 259 }; 260 var width = slider.offsetHeight; 261 var pageY = getPageY(input); 262 var relativeY = pageY - offset.top; 263 topPercent = (relativeY / width) * 100; 264 } 265 return topPercent; 266 } 267 var BOOLEAN_OPTIONS = { 268 animate: true, 269 showLabels: true, 270 showCredits: true, 271 makeResponsive: true 272 }; 273 function interpret_boolean(x) { 274 if (typeof x != "string") { 275 return Boolean(x); 276 } 277 return !(x === "false" || x === ""); 278 } 279 function JXSlider(selector, images, options) { 280 this.selector = selector; 281 var i; 282 this.options = { 283 animate: true, 284 showLabels: true, 285 showCredits: true, 286 makeResponsive: true, 287 startingPosition: "50%", 288 mode: "horizontal", 289 callback: null 290 }; 291 for (i in this.options) { 292 if (i in options) { 293 if (i in BOOLEAN_OPTIONS) { 294 this.options[i] = interpret_boolean(options[i]); 295 } else { 296 this.options[i] = options[i]; 297 } 298 } 299 } 300 if (images.length == 2) { 301 if (checkFlickr(images[0].src)) { 302 this.imgBefore = new FlickrGraphic(images[0], this); 303 } else { 304 this.imgBefore = new Graphic(images[0], this); 305 } 306 if (checkFlickr(images[1].src)) { 307 this.imgAfter = new FlickrGraphic(images[1], this); 308 } else { 309 this.imgAfter = new Graphic(images[1], this); 310 } 311 } else { 312 console.warn("The images parameter takes two Image objects."); 313 } 314 if (this.imgBefore.credit || this.imgAfter.credit) { 315 this.options.showCredits = true; 316 } else { 317 this.options.showCredits = false; 318 } 319 } 320 JXSlider.prototype = { 321 updateSlider: function(input, animate) { 322 var leftPercent, rightPercent; 323 if (this.options.mode === "vertical") { 324 leftPercent = getTopPercent(this.slider, input); 325 } else { 326 leftPercent = getLeftPercent(this.slider, input); 327 } 328 leftPercent = leftPercent.toFixed(2) + "%"; 329 leftPercentNum = parseFloat(leftPercent); 330 rightPercent = 100 - leftPercentNum + "%"; 331 if (leftPercentNum > 0 && leftPercentNum < 100) { 332 removeClass(this.handle, "transition"); 333 removeClass(this.rightImage, "transition"); 334 removeClass(this.leftImage, "transition"); 335 if (this.options.animate && animate) { 336 addClass(this.handle, "transition"); 337 addClass(this.leftImage, "transition"); 338 addClass(this.rightImage, "transition"); 339 } 340 if (this.options.mode === "vertical") { 341 this.handle.style.top = leftPercent; 342 this.leftImage.style.height = leftPercent; 343 this.rightImage.style.height = rightPercent; 344 } else { 345 this.handle.style.left = leftPercent; 346 this.leftImage.style.width = leftPercent; 347 this.rightImage.style.width = rightPercent; 348 } 349 this.sliderPosition = leftPercent; 350 } 351 }, 352 getPosition: function() { 353 return this.sliderPosition; 354 }, 355 displayLabel: function(element, labelText) { 356 label = document.createElement("div"); 357 label.className = "jx-label"; 358 label.setAttribute("tabindex", 0); 359 setText(label, labelText); 360 element.appendChild(label); 361 }, 362 displayCredits: function() { 363 credit = document.createElement("div"); 364 credit.className = "jx-credit"; 365 text = "<em>Photo Credits:</em>"; 366 if (this.imgBefore.credit) { 367 text += " <em>Before</em> " + this.imgBefore.credit; 368 } 369 if (this.imgAfter.credit) { 370 text += " <em>After</em> " + this.imgAfter.credit; 371 } 372 credit.innerHTML = text; 373 this.wrapper.appendChild(credit); 374 }, 375 setStartingPosition: function(s) { 376 this.options.startingPosition = s; 377 }, 378 checkImages: function() { 379 if ( 380 getImageDimensions(this.imgBefore.image).aspect() == 381 getImageDimensions(this.imgAfter.image).aspect() 382 ) { 383 return true; 384 } else { 385 return false; 386 } 387 }, 388 calculateDims: function(width, height) { 389 var ratio = getImageDimensions(this.imgBefore.image).aspect(); 390 if (width) { 391 height = width / ratio; 392 } else if (height) { 393 width = height * ratio; 394 } 395 return { width: width, height: height, ratio: ratio }; 396 }, 397 responsivizeIframe: function(dims) { 398 if (dims.height < window.innerHeight) { 399 if (dims.ratio >= 1) { 400 this.wrapper.style.paddingTop = 401 parseInt((window.innerHeight - dims.height) / 2) + "px"; 402 } 403 } else if (dims.height > window.innerHeight) { 404 dims = this.calculateDims(0, window.innerHeight); 405 this.wrapper.style.paddingLeft = 406 parseInt((window.innerWidth - dims.width) / 2) + "px"; 407 } 408 if (this.options.showCredits) { 409 dims.height -= 13; 410 } 411 return dims; 412 }, 413 setWrapperDimensions: function() { 414 var wrapperWidth = getComputedWidthAndHeight(this.wrapper).width; 415 var wrapperHeight = getComputedWidthAndHeight(this.wrapper).height; 416 var dims = this.calculateDims(wrapperWidth, wrapperHeight); 417 if ( 418 window.location !== window.parent.location && 419 !this.options.makeResponsive 420 ) { 421 dims = this.responsivizeIframe(dims); 422 } 423 this.wrapper.style.height = parseInt(dims.height) + "px"; 424 this.wrapper.style.width = parseInt(dims.width) + "px"; 425 }, 426 optimizeWrapper: function(maxWidth) { 427 var result = juxtapose.OPTIMIZATION_ACCEPTED; 428 if ( 429 this.imgBefore.image.naturalWidth >= maxWidth && 430 this.imgAfter.image.naturalWidth >= maxWidth 431 ) { 432 this.wrapper.style.width = maxWidth + "px"; 433 result = juxtapose.OPTIMIZATION_WAS_CONSTRAINED; 434 } else if (this.imgAfter.image.naturalWidth < maxWidth) { 435 this.wrapper.style.width = this.imgAfter.image.naturalWidth + "px"; 436 } else { 437 this.wrapper.style.width = this.imgBefore.image.naturalWidth + "px"; 438 } 439 this.setWrapperDimensions(); 440 return result; 441 }, 442 _onLoaded: function() { 443 if ( 444 this.imgBefore && 445 this.imgBefore.loaded === true && 446 this.imgAfter && 447 this.imgAfter.loaded === true 448 ) { 449 this.wrapper = document.querySelector(this.selector); 450 addClass(this.wrapper, "juxtapose"); 451 this.wrapper.style.width = getNaturalDimensions( 452 this.imgBefore.image 453 ).width; 454 this.setWrapperDimensions(); 455 this.slider = document.createElement("div"); 456 this.slider.className = "jx-slider"; 457 this.wrapper.appendChild(this.slider); 458 if (this.options.mode != "horizontal") { 459 addClass(this.slider, this.options.mode); 460 } 461 this.handle = document.createElement("div"); 462 this.handle.className = "jx-handle"; 463 this.rightImage = document.createElement("div"); 464 this.rightImage.className = "jx-image jx-right"; 465 this.rightImage.appendChild(this.imgAfter.image); 466 this.leftImage = document.createElement("div"); 467 this.leftImage.className = "jx-image jx-left"; 468 this.leftImage.appendChild(this.imgBefore.image); 469 this.labCredit = document.createElement("a"); 470 this.labCredit.setAttribute("href", "http://juxtapose.knightlab.com"); 471 this.labCredit.setAttribute("target", "_blank"); 472 this.labCredit.className = "jx-knightlab"; 473 this.labLogo = document.createElement("div"); 474 this.labLogo.className = "knightlab-logo"; 475 this.labCredit.appendChild(this.labLogo); 476 this.projectName = document.createElement("span"); 477 this.projectName.className = "juxtapose-name"; 478 setText(this.projectName, "JuxtaposeJS"); 479 this.labCredit.appendChild(this.projectName); 480 this.slider.appendChild(this.handle); 481 this.slider.appendChild(this.leftImage); 482 this.slider.appendChild(this.rightImage); 483 this.slider.appendChild(this.labCredit); 484 this.leftArrow = document.createElement("div"); 485 this.rightArrow = document.createElement("div"); 486 this.control = document.createElement("div"); 487 this.controller = document.createElement("div"); 488 this.leftArrow.className = "jx-arrow jx-left"; 489 this.rightArrow.className = "jx-arrow jx-right"; 490 this.control.className = "jx-control"; 491 this.controller.className = "jx-controller"; 492 this.controller.setAttribute("tabindex", 0); 493 this.controller.setAttribute("role", "slider"); 494 this.controller.setAttribute("aria-valuenow", 50); 495 this.controller.setAttribute("aria-valuemin", 0); 496 this.controller.setAttribute("aria-valuemax", 100); 497 this.handle.appendChild(this.leftArrow); 498 this.handle.appendChild(this.control); 499 this.handle.appendChild(this.rightArrow); 500 this.control.appendChild(this.controller); 501 this._init(); 502 } 503 }, 504 _init: function() { 505 if (this.checkImages() === false) { 506 console.warn( 507 this, 508 "Check that the two images have the same aspect ratio for the slider to work correctly." 509 ); 510 } 511 this.updateSlider(this.options.startingPosition, false); 512 if (this.options.showLabels === true) { 513 if (this.imgBefore.label) { 514 this.displayLabel(this.leftImage, this.imgBefore.label); 515 } 516 if (this.imgAfter.label) { 517 this.displayLabel(this.rightImage, this.imgAfter.label); 518 } 519 } 520 if (this.options.showCredits === true) { 521 this.displayCredits(); 522 } 523 var self = this; 524 window.addEventListener("resize", function() { 525 self.setWrapperDimensions(); 526 }); 527 this.slider.addEventListener("mousedown", function(e) { 528 e = e || window.event; 529 e.preventDefault(); 530 self.updateSlider(e, true); 531 animate = true; 532 this.addEventListener("mousemove", function(e) { 533 e = e || window.event; 534 e.preventDefault(); 535 if (animate) { 536 self.updateSlider(e, false); 537 } 538 }); 539 this.addEventListener("mouseup", function(e) { 540 e = e || window.event; 541 e.preventDefault(); 542 e.stopPropagation(); 543 this.removeEventListener("mouseup", arguments.callee); 544 animate = false;
545 }); 546 }); 547 this.slider.addEventListener("touchstart", function(e) { 548 e = e || window.event; 549 e.preventDefault(); 550 e.stopPropagation(); 551 self.updateSlider(e, true); 552 this.addEventListener("touchmove", function(e) { 553 e = e || window.event; 554 e.preventDefault(); 555 e.stopPropagation(); 556 self.updateSlider(event, false); 557 }); 558 }); 559 this.handle.addEventListener("keydown", function(e) { 560 e = e || window.event; 561 var key = e.which || e.keyCode; 562 var ariaValue = parseFloat(this.style.left); 563 if (key == 37) { 564 ariaValue = ariaValue - 1; 565 var leftStart = parseFloat(this.style.left) - 1; 566 self.updateSlider(leftStart, false); 567 self.controller.setAttribute("aria-valuenow", ariaValue); 568 } 569 if (key == 39) { 570 ariaValue = ariaValue + 1; 571 var rightStart = parseFloat(this.style.left) + 1; 572 self.updateSlider(rightStart, false); 573 self.controller.setAttribute("aria-valuenow", ariaValue); 574 } 575 }); 576 this.leftImage.addEventListener("keydown", function(event) { 577 var key = event.which || event.keyCode; 578 if (key == 13 || key == 32) { 579 self.updateSlider("90%", true); 580 self.controller.setAttribute("aria-valuenow", 90); 581 } 582 }); 583 this.rightImage.addEventListener("keydown", function(event) { 584 var key = event.which || event.keyCode; 585 if (key == 13 || key == 32) { 586 self.updateSlider("10%", true); 587 self.controller.setAttribute("aria-valuenow", 10); 588 } 589 }); 590 juxtapose.sliders.push(this); 591 if (this.options.callback && typeof this.options.callback == "function") { 592 this.options.callback(this); 593 } 594 } 595 }; 596 juxtapose.makeSlider = function(element, idx) { 597 if (typeof idx == "undefined") { 598 idx = juxtapose.sliders.length; 599 } 600 var w = element; 601 var images = w.querySelectorAll("img"); 602 var options = {}; 603 if (w.getAttribute("data-animate")) { 604 options.animate = w.getAttribute("data-animate"); 605 } 606 if (w.getAttribute("data-showlabels")) { 607 options.showLabels = w.getAttribute("data-showlabels"); 608 } 609 if (w.getAttribute("data-showcredits")) { 610 options.showCredits = w.getAttribute("data-showcredits"); 611 } 612 if (w.getAttribute("data-startingposition")) { 613 options.startingPosition = w.getAttribute("data-startingposition"); 614 } 615 if (w.getAttribute("data-mode")) { 616 options.mode = w.getAttribute("data-mode"); 617 } 618 if (w.getAttribute("data-makeresponsive")) { 619 options.mode = w.getAttribute("data-makeresponsive"); 620 } 621 specificClass = "juxtapose-" + idx; 622 addClass(element, specificClass); 623 selector = "." + specificClass; 624 if (w.innerHTML) { 625 w.innerHTML = ""; 626 } else { 627 w.innerText = ""; 628 } 629 slider = new juxtapose.JXSlider( 630 selector, 631 [ 632 { 633 src: images[0].src, 634 label: images[0].getAttribute("data-label"), 635 credit: images[0].getAttribute("data-credit"), 636 alt: images[0].alt 637 }, 638 { 639 src: images[1].src, 640 label: images[1].getAttribute("data-label"), 641 credit: images[1].getAttribute("data-credit"), 642 alt: images[1].alt 643 } 644 ], 645 options 646 ); 647 }; 648 juxtapose.scanPage = function() { 649 var elements = document.querySelectorAll(".juxtapose"); 650 for (var i = 0; i < elements.length; i++) { 651 juxtapose.makeSlider(elements[i], i); 652 } 653 }; 654 juxtapose.JXSlider = JXSlider; 655 window.juxtapose = juxtapose; 656 juxtapose.scanPage(); 657})(document, window); 658!window.addEventListener && 659 (function( 660 WindowPrototype, 661 DocumentPrototype, 662 ElementPrototype, 663 addEventListener, 664 removeEventListener, 665 dispatchEvent, 666 registry 667 ) { 668 WindowPrototype[addEventListener] = DocumentPrototype[ 669 addEventListener 670 ] = ElementPrototype[addEventListener] = function(type, listener) { 671 var target = this; 672 registry.unshift([ 673 target, 674 type, 675 listener, 676 function(event) { 677 event.currentTarget = target; 678 event.preventDefault = function() { 679 event.returnValue = false;
680 }; 681 event.stopPropagation = function() { 682 event.cancelBubble = true; 683 }; 684 event.target = event.srcElement || target; 685 listener.call(target, event); 686 } 687 ]); 688 this.attachEvent("on" + type, registry[0][3]); 689 }; 690 WindowPrototype[removeEventListener] = DocumentPrototype[ 691 removeEventListener 692 ] = ElementPrototype[removeEventListener] = function(type, listener) { 693 for (var index = 0, register; (register = registry[index]); ++index) { 694 if ( 695 register[0] == this && 696 register[1] == type && 697 register[2] == listener 698 ) { 699 return this.detachEvent("on" + type, registry.splice(index, 1)[0][3]); 700 } 701 } 702 }; 703 WindowPrototype[dispatchEvent] = DocumentPrototype[ 704 dispatchEvent 705 ] = ElementPrototype[dispatchEvent] = function(eventObject) { 706 return this.fireEvent("on" + eventObject.type, eventObject); 707 }; 708 })( 709 Window.prototype, 710 HTMLDocument.prototype, 711 Element.prototype, 712 "addEventListener", 713 "removeEventListener", 714 "dispatchEvent", 715 [] 716 );
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.