1/* 2 * Dics: Definitive image comparison slider. A multiple image vanilla comparison slider. 3 * 4 * By Abel Cabeza Román, a Codictados developer 5 * Src: https://github.com/abelcabezaroman/definitive-image-comparison-slider 6 * Example: http://codictados.com/portfolio/definitive-image-comparison-slider-demo/ 7 */ 8 9/** 10 * 11 */ 12 13/** 14 * 15 * @type {{container: null, filters: null, hideTexts: null, textPosition: string, linesOrientation: string, rotate: number, arrayBackgroundColorText: null, arrayColorText: null, linesColor: null}} 16 */ 17let defaultOptions = { 18 container: null, // **REQUIRED**: HTML container | `document.querySelector('.b-dics')` | 19 filters: null, // Array of CSS string filters |`['blur(3px)', 'grayscale(1)', 'sepia(1)', 'saturate(3)']` | 20 hideTexts: true, // Show text only when you hover the image container |`true`,`false`| 21 textPosition: "center", // Set the prefer text position |`'center'`,`'top'`, `'right'`, `'bottom'`, `'left'` | 22 linesOrientation: "horizontal", // Change the orientation of lines |`'horizontal'`,`'vertical'` | 23 rotate: 0, // Rotate the image container (not too useful but it's a beatiful effect. String of rotate CSS rule) |`'45deg'`| 24 arrayBackgroundColorText: null, // Change the bacground-color of sections texts with an array |`['#000000', '#FFFFFF']`| 25 arrayColorText: null, // Change the color of texts with an array |`['#FFFFFF', '#000000']`| 26 linesColor: null // Change the lines and arrows color |`'rgb(0,0,0)'`| 27 28}; 29 30/** 31 * 32 * @param options 33 * @constructor 34 */ 35let Dics = function(options) { 36 this.options = utils.extend({}, [defaultOptions, options], { 37 clearEmpty: true 38 }); 39 40 this.container = this.options.container; 41 42 if (this.container == null) { 43 console.error("Container element not found!"); 44 } else { 45 46 this._setOrientation(this.options.linesOrientation, this.container); 47 this.images = this._getImages(); 48 this.sliders = []; 49 this._activeSlider = null; 50 51 52 this._load(this.images[0]); 53 54 } 55}; 56 57 58/** 59 * 60 * @private 61 */ 62Dics.prototype._load = function(firstImage, maxCounter = 100000) { 63 if (firstImage.naturalWidth) { 64 this._buidAfterFirstImageLoad(firstImage); 65 window.addEventListener("resize", () => { 66 this._setContainerWidth(firstImage); 67 this._resetSizes(); 68 }); 69 70 } else { 71 if (maxCounter > 0) { 72 maxCounter--; 73 setTimeout(() => { 74 this._load(firstImage, maxCounter); 75 }, 100); 76 } else { 77 console.error("error loading images"); 78 } 79 80 } 81}; 82 83 84/** 85 * 86 * @private 87 */ 88Dics.prototype._buidAfterFirstImageLoad = function(firstImage) { 89 this._setContainerWidth(firstImage); 90 91 this._build(); 92 this._setEvents(); 93}; 94 95 96/** 97 * 98 * @private 99 */ 100Dics.prototype._setContainerWidth = function(firstImage) { 101 this.options.container.style.height = `${this._calcContainerHeight(firstImage)}px`; 102}; 103 104 105/** 106 * 107 * @private 108 */ 109Dics.prototype._setOpacityContainerForLoading = function(opacity) { 110 this.options.container.style.opacity = opacity; 111}; 112 113 114/** 115 * Reset sizes on window size change 116 * @private 117 */ 118Dics.prototype._resetSizes = function() { 119 let dics = this; 120 let imagesLength = dics.images.length; 121 122 let initialImagesContainerWidth = dics.container.getBoundingClientRect()[dics.config.sizeField] / imagesLength; 123 124 const sections$$ = dics.container.querySelectorAll("[data-function='b-dics__section']"); 125 for (let i = 0; i < sections$$.length; i++) { 126 let section$$ = sections$$[i]; 127 128 section$$.style.flex = `0 0 ${initialImagesContainerWidth}px`; 129 130 section$$.querySelector(".b-dics__image").style[this.config.positionField] = `${i * -initialImagesContainerWidth}px`; 131 132 const slider$$ = section$$.querySelector(".b-dics__slider"); 133 if (slider$$) { 134 slider$$.style[this.config.positionField] = `${initialImagesContainerWidth * (i + 1)}px`; 135 136 } 137 138 } 139 140}; 141 142/** 143 * Build HTML 144 * @private 145 */ 146Dics.prototype._build = function() { 147 let dics = this; 148 149 dics._applyGlobalClass(dics.options); 150 151 let imagesLength = dics.images.length; 152 153 154 let initialImagesContainerWidth = dics.container.getBoundingClientRect()[dics.config.sizeField] / imagesLength; 155 156 for (let i = 0; i < imagesLength; i++) { 157 let image = dics.images[i]; 158 let section = dics._createElement("div", "b-dics__section"); 159 let imageContainer = dics._createElement("div", "b-dics__image-container"); 160 let slider = dics._createSlider(i, initialImagesContainerWidth); 161 162 dics._createAltText(image, i, imageContainer); 163 164 dics._applyFilter(image, i, dics.options.filters); 165 dics._rotate(image, imageContainer); 166 167 168 section.setAttribute("data-function", "b-dics__section"); 169 section.style.flex = `0 0 ${initialImagesContainerWidth}px`; 170 171 image.classList.add("b-dics__image"); 172 173 section.appendChild(imageContainer); 174 imageContainer.appendChild(image); 175 176 if (i < imagesLength - 1) { 177 section.appendChild(slider); 178 } 179 180 dics.container.appendChild(section); 181 182 image.style[this.config.positionField] = `${i * -initialImagesContainerWidth}px`; 183 184 185 } 186 187 this.sections = this._getSections(); 188 this._setOpacityContainerForLoading(1); 189}; 190 191 192/** 193 * 194 * @returns {NodeListOf<SVGElementTagNameMap[string]>
194 | NodeListOf<HTMLElementTagNameMap[string]> | NodeListOf<Element>} 195 * @private 196 */ 197Dics.prototype._getImages = function() { 198 return this.container.querySelectorAll("img"); 199}; 200 201 202/** 203 * 204 * @returns {NodeListOf<SVGElementTagNameMap[string]> | NodeListOf<HTMLElementTagNameMap[string]> | NodeListOf<Element>} 205 * @private 206 */ 207Dics.prototype._getSections = function() { 208 return this.container.querySelectorAll("[data-function=\"b-dics__section\"]"); 209}; 210 211/** 212 * 213 * @param elementClass 214 * @param className 215 * @returns {HTMLElement | HTMLSelectElement | HTMLLegendElement | HTMLTableCaptionElement | HTMLTextAreaElement | HTMLModElement | HTMLHRElement | HTMLOutputElement | HTMLPreElement | HTMLEmbedElement | HTMLCanvasElement | HTMLFrameSetElement | HTMLMarqueeElement | HTMLScriptElement | HTMLInputElement | HTMLUnknownElement | HTMLMetaElement | HTMLStyleElement | HTMLObjectElement | HTMLTemplateElement | HTMLBRElement | HTMLAudioElement | HTMLIFrameElement | HTMLMapElement | HTMLTableElement | HTMLAnchorElement | HTMLMenuElement | HTMLPictureElement | HTMLParagraphElement | HTMLTableDataCellElement | HTMLTableSectionElement | HTMLQuoteElement | HTMLTableHeaderCellElement | HTMLProgressElement | HTMLLIElement | HTMLTableRowElement | HTMLFontElement | HTMLSpanElement | HTMLTableColElement | HTMLOptGroupElement | HTMLDataElement | HTMLDListElement | HTMLFieldSetElement | HTMLSourceElement | HTMLBodyElement | HTMLDirectoryElement | HTMLDivElement | HTMLUListElement | HTMLHtmlElement | HTMLAreaElement | HTMLMeterElement | HTMLAppletElement | HTMLFrameElement | HTMLOptionElement | HTMLImageElement | HTMLLinkElement | HTMLHeadingElement | HTMLSlotElement | HTMLVideoElement | HTMLBaseFontElement | HTMLTitleElement | HTMLButtonElement | HTMLHeadElement | HTMLParamElement | HTMLTrackElement | HTMLOListElement | HTMLDataListElement | HTMLLabelElement | HTMLFormElement | HTMLTimeElement | HTMLBaseElement} 216 * @private 217 */ 218Dics.prototype._createElement = function(elementClass, className) { 219 let newElement = document.createElement(elementClass); 220 221 newElement.classList.add(className); 222 223 return newElement; 224}; 225 226/** 227 * Set need DOM events 228 * @private 229 */ 230Dics.prototype._setEvents = function() { 231 let dics = this; 232 233 dics._disableImageDrag(); 234 235 dics._isGoingRight = null; 236 237 let oldx = 0; 238 239 let listener = function(event) { 240 241 let xPageCoord = event.pageX ? event.pageX : event.touches[0].pageX; 242 243 if (xPageCoord < oldx) { 244 dics._isGoingRight = false; 245 } else if (xPageCoord > oldx) { 246 dics._isGoingRight = true; 247 } 248 249 oldx = xPageCoord; 250 251 let position = dics._calcPosition(event); 252 253 let beforeSectionsWidth = dics._beforeSectionsWidth(dics.sections, dics.images, dics._activeSlider); 254 255 let calcMovePixels = position - beforeSectionsWidth; 256 257 dics.sliders[dics._activeSlider].style[dics.config.positionField] = `${position}px`; 258 259 dics._pushSections(calcMovePixels, position); 260 }; 261 262 dics.container.addEventListener("click", listener); 263 264 for (let i = 0; i < dics.sliders.length; i++) { 265 let slider = dics.sliders[i]; 266 utils.setMultiEvents(slider, ["mousedown", "touchstart"], function(event) { 267 dics._activeSlider = i; 268 269 dics._clickPosition = dics._calcPosition(event); 270 271 slider.classList.add("b-dics__slider--active"); 272 273 utils.setMultiEvents(dics.container, ["mousemove", "touchmove"], listener); 274 }); 275 } 276 277 278 let listener2 = function() { 279 let activeElements = dics.container.querySelectorAll(".b-dics__slider--active"); 280 281 for (let activeElement of activeElements) { 282 activeElement.classList.remove("b-dics__slider--active"); 283 utils.removeMultiEvents(dics.container, ["mousemove", "touchmove"], listener); 284 } 285 }; 286 287 utils.setMultiEvents(document.body, ["mouseup", "touchend"], listener2); 288 289 290}; 291 292/** 293 * 294 * @param sections 295 * @param images 296 * @param activeSlider 297 * @returns {number} 298 * @private 299 */ 300Dics.prototype._beforeSectionsWidth = function(sections, images, activeSlider) { 301 let width = 0; 302 for (let i = 0; i < sections.length; i++) { 303 let section = sections[i]; 304 if (i !== activeSlider) { 305 width += section.getBoundingClientRect()[this.config.sizeField]; 306 } else { 307 return width; 308 } 309 } 310}; 311 312/** 313 * 314 * @returns {number} 315 * @private 316 */ 317Dics.prototype._calcContainerHeight = function(firstImage) { 318 let imgHeight = firstImage.naturalHeight;
319 let imgWidth = firstImage.naturalWidth; 320 let containerWidth = this.options.container.getBoundingClientRect().width; 321 322 return (containerWidth / imgWidth) * imgHeight; 323}; 324 325 326/** 327 * 328 * @param sections 329 * @param images 330 * @private 331 */ 332Dics.prototype._setLeftToImages = function(sections, images) { 333 let size = 0; 334 for (let i = 0; i < images.length; i++) { 335 let image = images[i]; 336 337 image.style[this.config.positionField] = `-${size}px`; 338 size += sections[i].getBoundingClientRect()[this.config.sizeField]; 339 340 this.sliders[i].style[this.config.positionField] = `${size}px`; 341 342 } 343}; 344 345 346/** 347 * 348 * @private 349 */ 350Dics.prototype._disableImageDrag = function() { 351 for (let i = 0; i < this.images.length; i++) { 352 this.sliders[i].addEventListener("dragstart", function(e) { 353 e.preventDefault(); 354 }); 355 this.images[i].addEventListener("dragstart", function(e) { 356 e.preventDefault(); 357 }); 358 } 359}; 360 361/** 362 * 363 * @param image 364 * @param index 365 * @param filters 366 * @private 367 */ 368Dics.prototype._applyFilter = function(image, index, filters) { 369 if (filters) { 370 image.style.filter = filters[index]; 371 } 372}; 373 374/** 375 * 376 * @param options 377 * @private 378 */ 379Dics.prototype._applyGlobalClass = function(options) { 380 let container = options.container; 381 382 383 if (options.hideTexts) { 384 container.classList.add("b-dics--hide-texts"); 385 } 386 387 if (options.linesOrientation === "vertical") { 388 container.classList.add("b-dics--vertical"); 389 } 390 391 if (options.textPosition === "center") { 392 container.classList.add("b-dics--tp-center"); 393 } else if (options.textPosition === "bottom") { 394 container.classList.add("b-dics--tp-bottom"); 395 } else if (options.textPosition === "left") { 396 container.classList.add("b-dics--tp-left"); 397 } else if (options.textPosition === "right") { 398 container.classList.add("b-dics--tp-right"); 399 } 400}; 401 402 403Dics.prototype._createSlider = function(i, initialImagesContainerWidth) { 404 let slider = this._createElement("div", "b-dics__slider"); 405 406 if (this.options.linesColor) { 407 slider.style.color = this.options.linesColor; 408 } 409 410 slider.style[this.config.positionField] = `${initialImagesContainerWidth * (i + 1)}px`; 411 412 this.sliders.push(slider); 413 414 415 return slider; 416}; 417 418 419/** 420 * 421 * @param image 422 * @param i 423 * @param imageContainer 424 * @private 425 */ 426Dics.prototype._createAltText = function(image, i, imageContainer) { 427 let textContent = image.getAttribute("alt"); 428 if (textContent) { 429 let text = this._createElement("p", "b-dics__text"); 430 431 if (this.options.arrayBackgroundColorText) { 432 text.style.backgroundColor = this.options.arrayBackgroundColorText[i]; 433 } 434 if (this.options.arrayColorText) { 435 text.style.color = this.options.arrayColorText[i]; 436 } 437 438 text.appendChild(document.createTextNode(textContent)); 439 440 imageContainer.appendChild(text); 441 } 442}; 443 444 445/** 446 * 447 * @param image 448 * @param imageContainer 449 * @private 450 */ 451Dics.prototype._rotate = function(image, imageContainer) { 452 image.style.rotate = `-${this.options.rotate}`; 453 imageContainer.style.rotate = this.options.rotate; 454 455}; 456 457 458/** 459 * 460 * @private 461 */ 462Dics.prototype._removeActiveElements = function() { 463 let activeElements = Dics.container.querySelectorAll(".b-dics__slider--active"); 464 465 for (let activeElement of activeElements) { 466 activeElement.classList.remove("b-dics__slider--active"); 467 utils.removeMultiEvents(Dics.container, ["mousemove", "touchmove"], Dics.prototype._removeActiveElements); 468 } 469}; 470 471 472/** 473 * 474 * @param linesOrientation 475 * @private 476 */ 477Dics.prototype._setOrientation = function(linesOrientation) { 478 this.config = {}; 479 480 if (linesOrientation === "vertical") { 481 this.config.offsetSizeField = "offsetHeight"; 482 this.config.offsetPositionField = "offsetTop"; 483 this.config.sizeField = "height"; 484 this.config.positionField = "top"; 485 this.config.clientField = "clientY"; 486 this.config.pageField = "pageY"; 487 } else { 488 this.config.offsetSizeField = "offsetWidth"; 489 this.config.offsetPositionField = "offsetLeft"; 490 this.config.sizeField = "width"; 491 this.config.positionField = "left"; 492 this.config.clientField = "clientX"; 493 this.config.pageField = "pageX"; 494 } 495 496 497}; 498 499 500/** 501 * 502 * @param event 503 * @returns {number} 504 * @private 505 */ 506Dics.prototype._calcPosition = function(event) { 507 let containerCoords = this.container.getBoundingClientRect(); 508 let pixel = !isNaN(event[this.config.clientField]) ? event[this.config.clientField] : event.touches[0][this.config.clientField]; 509 510 return containerCoords[this.config.positionField] < pixel ? pixel - containerCoords[this.config.positionField] : 0; 511}; 512 513 514/** 515 * 516 * @private 517 */ 518Dics.prototype._pushSections = function(calcMovePixels, position) { 519 // if (this._rePosUnderActualSections(position)) { 520 this._setFlex(position, this._isGoingRight); 521 522 let section = this.sections[this._activeSlider]; 523 let postActualSection = this.sections[this._activeSlider + 1]; 524 let sectionWidth = postActualSection.getBoundingClientRect()[this.config.sizeField] - (calcMovePixels - this.sections[this._activeSlider].getBoundingClientRect()[this.config.sizeField]); 525 526 527 section.style.flex = this._isGoingRight === true ? `2 0 ${calcMovePixels}px` : `1 1 ${calcMovePixels}px`; 528 postActualSection.style.flex = this._isGoingRight === true ? ` ${sectionWidth}px` : `2 0 ${sectionWidth}px`; 529 530 this._setLeftToImages(this.sections, this.images); 531 532 // } 533}; 534 535 536/** 537 * 538 * @private 539 */ 540Dics.prototype._setFlex = function(position, isGoingRight) { 541 let beforeSumSectionsSize = 0; 542 543 544 for (let i = 0; i < this.sections.length; i++) { 545 let section = this.sections[i]; 546 const sectionSize = section.getBoundingClientRect()[this.config.sizeField]; 547 548 beforeSumSectionsSize += sectionSize; 549 550 if ((isGoingRight && position > (beforeSumSectionsSize - sectionSize) && i > this._activeSlider) || (!isGoingRight && position < beforeSumSectionsSize) && i < this._activeSlider) { 551 section.style.flex = `1 100 ${sectionSize}px`; 552 } else { 553 section.style.flex = `0 0 ${sectionSize}px`; 554 } 555 556 } 557}; 558 559 560/** 561 * 562 * @type {{extend: (function(*=, *, *): *), setMultiEvents: setMultiEvents, removeMultiEvents: removeMultiEvents, getConstructor: (function(*=): string)}} 563 */ 564let utils = { 565 566 567 /** 568 * Native extend object 569 * @param target 570 * @param objects 571 * @param options 572 * @returns {*} 573 */ 574 extend: function(target, objects, options) { 575 576 for (let object in objects) { 577 if (objects.hasOwnProperty(object)) { 578 recursiveMerge(target, objects[object]); 579 } 580 } 581 582 function recursiveMerge (target, object) { 583 for (let property in object) { 584 if (object.hasOwnProperty(property)) { 585 let current = object[property]; 586 if (utils.getConstructor(current) === "Object") { 587 if (!target[property]) { 588 target[property] = {}; 589 } 590 recursiveMerge(target[property], current); 591 } else { 592 // clearEmpty 593 if (options.clearEmpty) { 594 if (current == null) { 595 continue; 596 } 597 } 598 target[property] = current; 599 } 600 } 601 } 602 } 603 604 return target; 605 }, 606 607 608 /** 609 * Set Multi addEventListener 610 * @param element 611 * @param events 612 * @param func 613 */ 614 setMultiEvents: function(element, events, func) { 615 for (let i = 0; i < events.length; i++) { 616 element.addEventListener(events[i], func); 617 } 618 }, 619 620 621 /** 622 * 623 * @param element 624 * @param events 625 * @param func 626 */ 627 removeMultiEvents: function(element, events, func) { 628 for (let i = 0; i < events.length; i++) { 629 element.removeEventListener(events[i], func, false); 630 } 631 }, 632 633 634 /**
635 * Get object constructor 636 * @param object 637 * @returns {string} 638 */ 639 getConstructor: function(object) { 640 return Object.prototype.toString.call(object).slice(8, -1); 641 } 642}; 643
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.