1/*! 2 * zoomist.js v1.0.2 3 * https://github.com/cotton123236/zoomist#readme 4 * 5 * Copyright 2021-present Wilson Wu 6 * Released under the MIT license 7 * 8 * Date: 2022-03-26T09:56:17.746Z 9 */ 10 11(function (global, factory) { 12 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 13 typeof define === 'function' && define.amd ? define(factory) : 14 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Zoomist = factory()); 15})(this, (function () { 'use strict'; 16 17 const NAME = 'zoomist'; 18 const MODULES$1 = ['slider', 'zoomer']; 19 const CLASS_CONTAINER = `${NAME}-container`; 20 const CLASS_WRAPPER = `${NAME}-wrapper`; 21 const CLASS_IMAGE = `${NAME}-image`; 22 const CLASS_SLIDER = `${NAME}-slider`; 23 const CLASS_SLIDER_MAIN = `${NAME}-slider-main`; 24 const CLASS_SLIDER_BAR = `${NAME}-slider-bar`; 25 const CLASS_SLIDER_BUTTON = `${NAME}-slider-button`; 26 const CLASS_ZOOMER = `${NAME}-zoomer`; 27 const CLASS_ZOOMER_IN = `${NAME}-in-zoomer`; 28 const CLASS_ZOOMER_OUT = `${NAME}-out-zoomer`; 29 const CLASS_ZOOMER_DISABLE = `${NAME}-zoomer-disable`; 30 const IS_BROWSER = typeof window !== 'undefined' && typeof window.document !== 'undefined'; 31 const IS_TOUCH = IS_BROWSER && 'ontouchstart' in window ? true : false; 32 const EVENT_TOUCH_START = IS_TOUCH ? 'touchstart' : 'mousedown'; 33 const EVENT_TOUCH_MOVE = IS_TOUCH ? 'touchmove' : 'mousemove'; 34 const EVENT_TOUCH_END = IS_TOUCH ? 'touchend' : 'mouseup'; 35 const EVENT_RESIZE = 'resize'; 36 const EVENT_WHEEL = 'wheel'; 37 38 var DEFAULT_OPTIONS = { 39 // {String} set initial image status 40 fill: 'cover', 41 // {String / querySelector} the attribute of image source or a image element 42 src: 'data-zoomist-src', 43 // {Boolean} set is draggable or not 44 draggable: true, 45 // {Boolean} set is wheelable or not 46 wheelable: true, 47 // {Boolean} set is pinchable or not 48 pinchable: true, 49 // {Boolean} set image can be drag out of the bounds (it will set to false when fill is contain) 50 bounds: true, 51 // {Number} the ratio of zoom at one time 52 zoomRatio: 0.1, 53 // {Number > 1, False} the max ratio of the image (compare to the initial image status) 54 maxRatio: false, 55 // {Boolean / String} 56 height: 'auto' 57 }; 58 const DEFAULT_SLIDER_OPTIONS = { 59 // {String / querySelector} the css selector string or a element of the slider 60 el: CLASS_SLIDER, 61 // {String} the direction of the slider 'horizontal' or 'vertical' 62 direction: 'horizontal', 63 // {Number} the max ratio of the slider (only work on options.maxRatio = false) 64 maxRatio: 2 65 }; 66 const DEFAULT_ZOOMER_OPTIONS = { 67 // {String / querySelector} the css selector string or a element of the in zoomer 68 inEl: CLASS_ZOOMER_IN, 69 // {String / querySelector} the css selector string or a element of the out zoomer 70 outEl: CLASS_ZOOMER_OUT, 71 // {Boolean} in zoomer and out zoomer will be disabled when image comes to maximin or minimum 72 disableOnBounds: true 73 }; 74 const EVENTS = { 75 // invoked when zoomist instance ready 76 ready: null, 77 // invoked when image is zooming 78 zoom: null, 79 // invoked when wheeling 80 wheel: null, 81 // invoked when mousedown on wrapper 82 dragStart: null, 83 // invoked when dragging the image 84 drag: null, 85 // invoked when mouseup on wrapper 86 dragEnd: null, 87 // invoked when mousedown on wrapper 88 pinchStart: null, 89 // invoked when pinching the image 90 pinch: null, 91 // invoked when mouseup on wrapper 92 pinchEnd: null, 93 // invoked when mousedown on slider 94 slideStart: null, 95 // invoked when sliding the slider 96 slide: null, 97 // invoked when mouseup on slider 98 slideEnd: null, 99 // invoked when image changes it's size 100 resize: null, 101 // invoked when reset methods be used 102 reset: null, 103 // invoked when destroy methods be used 104 destroy: null, 105 // invoked when update methods be used 106 update: null 107 }; 108 109 const isObject = value => { 110 return typeof value === 'object' && value !== null; 111 }; // check value is a plain object 112 113 const isPlainObject = value => { 114 if (!isObject(value)) return false;
115 116 try { 117 const { 118 constructor 119 } = value; 120 const { 121 prototype 122 } = constructor; 123 const { 124 hasOwnProperty 125 } = Object.prototype; 126 return constructor && prototype && hasOwnProperty.call(prototype, 'isPrototypeOf'); 127 } catch (error) { 128 return false; 129 } 130 }; // set object key and value 131 132 const setObject = (obj, value) => { 133 if (!obj) obj = {}; 134 135 for (const [k, v] of Object.entries(value)) { 136 obj[k] = v; 137 } 138 }; // make a new object from old object 139 140 const getNewObject = value => { 141 return Object.assign({}, value); 142 }; // check value is string and not empty 143 144 const isString = value => { 145 return typeof value === 'string' && value !== ''; 146 }; // check value is number and not NaN 147 148 const isNumber = value => { 149 return typeof value === 'number' && !isNaN(value); 150 }; // check element is exist 151 152 const isElementExist = value => { 153 return getElement(value) !== null; 154 }; // if value is an element then return value, if not then query value 155 156 const getElement = value => { 157 return value instanceof HTMLElement ? value : document.querySelector(value); 158 }; // check value is img tag or not 159 160 const isImg = value => { 161 return isElementExist(value) && getElement(value).tagName === 'IMG'; 162 }; // check value is a function 163 164 const isFunction = value => { 165 return typeof value === 'function'; 166 }; // check value is percentage 167 168 const isPercentage = value => { 169 return value.indexOf('%') > -1; 170 }; // get elemant style 171 172 const setStyle = (element, obj) => { 173 for (const [k, v] of Object.entries(obj)) { 174 element.style[k] = isNumber(v) ? `${v}px` : v; 175 } 176 }; // get mouse pageX and pageY 177 178 const getPointer = event => { 179 const isNotTouch = !IS_TOUCH || event.type === 'wheel'; 180 return { 181 x: isNotTouch ? event.pageX : event.touches[0].pageX, 182 y: isNotTouch ? event.pageY : event.touches[0].pageY, 183 clientX: isNotTouch ? event.clientX : event.touches[0].clientX, 184 clientY: isNotTouch ? event.clientY : event.touches[0].clientY 185 }; 186 }; // get transformX 187 188 const getTransformX = target => { 189 const transform = getComputedStyle(target).transform; 190 let mat = transform.match(/^matrix3d\((.+)\)$/); 191 if (mat) return parseFloat(mat[1].split(', ')[12]); 192 mat = transform.match(/^matrix\((.+)\)$/); 193 return mat ? parseFloat(mat[1].split(', ')[4]) : 0; 194 }; // get transformY 195 196 const getTransformY = target => { 197 const transform = getComputedStyle(target).transform; 198 let mat = transform.match(/^matrix3d\((.+)\)$/); 199 if (mat) return parseFloat(mat[1].split(', ')[13]); 200 mat = transform.match(/^matrix\((.+)\)$/); 201 return mat ? parseFloat(mat[1].split(', ')[5]) : 0; 202 }; // like .toFixed(2) 203 204 const roundToTwo = value => { 205 return +(Math.round(value + "e+2") + "e-2"); 206 }; // limit value 207 208 const minmax = (value, min, max) => { 209 return Math.min(Math.max(value, min), max); 210 }; // first letter to uppercase 211 212 const upperFirstLetter = value => { 213 return value.charAt(0).toUpperCase() + value.slice(1); 214 }; 215 216 var METHODS = { 217 /** 218 * get container (element) data 219 * @returns {Object} 220 */ 221 getContainerData() { 222 return getNewObject(this.data.containerData); 223 }, 224 225 /** 226 * get image data 227 * @returns {Object} 228 */ 229 getImageData() { 230 return getNewObject(this.data.imageData); 231 }, 232 233 /** 234 * get slider value 235 * @returns {Number} 236 */ 237 getSliderValue() { 238 return this.__modules__.slider?.value; 239 }, 240 241 /** 242 * get now zoom ratio 243 * @returns {Number} 244 */ 245 getZoomRatio() { 246 return this.ratio; 247 }, 248 249 /** 250 * zoom 251 * zoomRatio - zoomin when pass a positive number, zoomout when pass a negative number 252 * pointer - a object which return from getPoiner() 253 * @param {Number, Object} 254 */ 255 zoom(zoomRatio, pointer) { 256 const { 257 image, 258 data, 259 options, 260 ratio 261 } = this; 262 const { 263 bounds, 264 maxRatio 265 } = options; 266 if (bounds && ratio === 1 && zoomRatio < 0) return; 267 if (maxRatio && ratio === maxRatio && zoomRatio > 0) return; 268 const { 269 originImageData 270 } = data; 271 const containerData = this.getContainerData(); 272 const imageData = this.getImageData(); 273 const imageRect = image.getBoundingClientRect(); 274 const calcRatio = roundToTwo(ratio * (zoomRatio + 1)); 275 const newRatio = bounds && calcRatio < 1 ? 1 : maxRatio && calcRatio > maxRatio ? maxRatio : calcRatio;
276 const newZoomRatio = newRatio / ratio - 1; 277 const newWidth = originImageData.width * newRatio; 278 const newHeight = originImageData.height * newRatio; 279 const newLeft = (containerData.width - newWidth) / 2; 280 const newTop = (containerData.height - newHeight) / 2; 281 const distanceX = pointer ? (imageData.width / 2 - pointer.clientX + imageRect.left) * newZoomRatio + getTransformX(image) : getTransformX(image); 282 const distanceY = pointer ? (imageData.height / 2 - pointer.clientY + imageRect.top) * newZoomRatio + getTransformY(image) : getTransformY(image); 283 const transformX = bounds ? minmax(distanceX, newLeft, -newLeft) : distanceX; 284 const transformY = bounds ? minmax(distanceY, newTop, -newTop) : distanceY; 285 const newData = { 286 width: newWidth, 287 height: newHeight, 288 left: newLeft, 289 top: newTop 290 }; 291 setObject(data.imageData, newData); 292 setStyle(image, { ...newData, 293 transform: `translate(${transformX}px, ${transformY}px)` 294 }); 295 this.ratio = newRatio; 296 this.emit('zoom', newRatio); // if has slider 297 298 if (options.slider) { 299 const { 300 slider 301 } = this.__modules__; 302 const ratioPercentage = roundToTwo(1 - (slider.maxRatio - newRatio) / (slider.maxRatio - 1)) * 100; 303 slider.value = minmax(ratioPercentage, 0, 100); 304 this.slideTo(ratioPercentage, true); 305 } // if zoomer disableOnBounds 306 307 308 if (options.zoomer) { 309 const { 310 zoomer 311 } = this.__modules__; 312 313 if (zoomer.disableOnBounds) { 314 const { 315 bounds 316 } = options; 317 const { 318 zoomerInEl, 319 zoomerOutEl 320 } = this.__modules__.zoomer; 321 bounds && this.ratio === 1 ? zoomerOutEl.classList.add(CLASS_ZOOMER_DISABLE) : zoomerOutEl.classList.remove(CLASS_ZOOMER_DISABLE); 322 this.ratio === maxRatio ? zoomerInEl.classList.add(CLASS_ZOOMER_DISABLE) : zoomerInEl.classList.remove(CLASS_ZOOMER_DISABLE); 323 } 324 } 325 326 return this; 327 }, 328 329 /** 330 * zoomTo (zoom to a specific ratio) 331 * zoomRatio - zoomin when pass a number more than 1, zoomout when pass a number less than 1 332 * @param {Number} 333 */ 334 zoomTo(zoomRatio) { 335 const { 336 ratio 337 } = this; 338 339 if (zoomRatio !== ratio) { 340 const calcRatio = zoomRatio / ratio - 1; 341 this.zoom(calcRatio); 342 } 343 344 return this; 345 }, 346 347 move(x = 0, y = 0) { 348 const { 349 image, 350 data, 351 options 352 } = this; 353 const { 354 imageData, 355 dragData 356 } = data; 357 const { 358 top, 359 left 360 } = imageData; 361 const { 362 transX, 363 transY 364 } = dragData; 365 const { 366 bounds 367 } = options; 368 const calcTransX = bounds ? minmax(transX - x, left, -left) : transX - x; 369 const calcTransY = bounds ? minmax(transY - y, top, -top) : transY - y; 370 const newTransX = roundToTwo(calcTransX); 371 const newTransY = roundToTwo(calcTransY); 372 setObject(dragData, { 373 transX: newTransX, 374 transY: newTransY 375 }); 376 image.style.transform = `translate(${newTransX}px, ${newTransY}px)`; 377 return this; 378 }, 379 380 moveTo(x, y) { 381 const { 382 data, 383 options 384 } = this; 385 const { 386 imageData, 387 dragData 388 } = data; 389 const { 390 top, 391 left 392 } = imageData; 393 const { 394 transX, 395 transY 396 } = dragData; 397 const { 398 bounds 399 } = options; 400 x = x ?? Math.abs(left); 401 y = y ?? Math.abs(top); 402 const calcTransX = bounds ? minmax(left + x + transX, left, -left) : left + x + transX; 403 const calcTransY = bounds ? minmax(top + y + transY, top, -top) : top + y + transY; 404 this.move(calcTransX, calcTransY); 405 return this; 406 }, 407 408 /** 409 * slideTo (only work on the slider) 410 * value - a numer between 0-100 411 * @param {Number} 412 */ 413 slideTo(value, onlySlide) { 414 const { 415 __modules__ 416 } = this; 417 if (!__modules__.slider) return; 418 const { 419 slider 420 } = __modules__; 421 const position = slider.direction === 'horizontal' ? 'left' : 'top'; 422 const symbol = slider.direction === 'horizontal' ? '' : '-'; 423 const distance = minmax(value, 0, 100); 424 slider.sliderButton.style[position] = `${symbol}${distance}%`; 425 426 if (!onlySlide) { 427 const percentage = distance / 100; 428 const minRatio = this.ratio < 1 ? this.ratio : 1; 429 const maxRatio = this.ratio > slider.maxRatio ? this.ratio : slider.maxRatio; 430 const ratio = (maxRatio - minRatio) * percentage + minRatio;
431 this.zoomTo(ratio); 432 } 433 434 return this; 435 }, 436 437 /** 438 * reset image to initial status 439 */ 440 reset() { 441 const { 442 image 443 } = this; 444 this.zoomTo(1); 445 setStyle(image, { 446 transform: 'translate(0, 0)' 447 }); 448 this.emit('reset'); 449 return this; 450 }, 451 452 /** 453 * destory the instance of zoomist 454 */ 455 destroy() { 456 const { 457 element, 458 wrapper 459 } = this; 460 const { 461 slider, 462 zoomer 463 } = this.__modules__; 464 element[NAME] = undefined; 465 this.mounted = false; 466 if (slider) this.destroySlider(); 467 if (zoomer) this.destroyZoomer(); 468 wrapper.remove(); 469 element.style.removeProperty('width'); 470 element.style.removeProperty('padding-bottom'); 471 element.classList.remove(CLASS_CONTAINER); 472 this.emit('destroy'); 473 return this; 474 }, 475 476 /** 477 * a syntactic sugar of destroy and init 478 */ 479 update() { 480 this.destroy().init(); 481 this.emit('update'); 482 return this; 483 }, 484 485 /** 486 * add handler on __events__ 487 * @param {String} events 488 * @param {Function} handler 489 */ 490 on(events, handler) { 491 if (!isFunction(handler)) return this; 492 const { 493 __events__ 494 } = this; 495 events.split(' ').forEach(evt => { 496 if (!__events__[evt]) __events__[evt] = []; 497 498 __events__[evt].push(handler); 499 }); 500 return this; 501 }, 502 503 /** 504 * invoke handlers in __events__[event] 505 * @param {String, ...} args 506 */ 507 emit(...args) { 508 const { 509 __events__ 510 } = this; 511 const event = args[0]; 512 const data = args.slice(1, args.length); 513 if (!__events__[event]) return this; 514 515 __events__[event].forEach(handler => { 516 if (isFunction(handler)) handler.apply(this, data); 517 }); 518 519 return this; 520 } 521 522 }; 523 524 var bindEvents = (zoomist => { 525 const { 526 element, 527 wrapper, 528 image, 529 options, 530 data 531 } = zoomist; 532 const { 533 containerData, 534 imageData, 535 originImageData 536 } = data; // set image size on window resize 537 538 const resize = () => { 539 if (containerData.width === element.offsetWidth) return; 540 const widthRatio = element.offsetWidth / containerData.width; 541 const heightRatio = element.offsetHeight / containerData.height; 542 const originImageWidth = originImageData.width * widthRatio; 543 const originImageHeight = originImageData.height * heightRatio; 544 const originImageLeft = originImageData.left * widthRatio; 545 const originImageTop = originImageData.top * heightRatio; 546 const imageWidth = imageData.width * widthRatio; 547 const imageHeight = imageData.height * heightRatio; 548 const imageLeft = imageData.left * widthRatio; 549 const imageTop = imageData.top * heightRatio; 550 const transformX = getTransformX(image) * widthRatio; 551 const transformY = getTransformY(image) * heightRatio; 552 setObject(containerData, { 553 width: element.offsetWidth, 554 height: element.offsetHeight 555 }); 556 setObject(originImageData, { 557 width: originImageWidth, 558 height: originImageHeight, 559 left: originImageLeft, 560 top: originImageTop 561 }); 562 setObject(imageData, { 563 width: imageWidth, 564 height: imageHeight, 565 left: imageLeft, 566 top: imageTop 567 }); 568 setStyle(zoomist.image, { 569 width: imageWidth, 570 height: imageHeight, 571 left: imageLeft, 572 top: imageTop, 573 transform: `translate(${transformX}px, ${transformY}px)` 574 }); 575 zoomist.emit('resize'); 576 }; 577 578 window.addEventListener(EVENT_RESIZE, resize); // set image drag event 579 580 zoomist.dragging = false; 581 zoomist.data.dragData = { 582 startX: 0, 583 startY: 0, 584 transX: 0, 585 transY: 0 586 }; 587 if (options.fill === 'contain' && options.bounds) options.bounds = false; 588 const { 589 dragData 590 } = data; 591 592 const dragStart = e => { 593 if (!options.draggable) return; 594 if (e.which === 2 || e.which === 3) return; 595 const isPinch = e.touches && e.touches.length === 2; 596 setObject(dragData, { 597 startX: isPinch ? (e.touches[0].pageX + e.touches[1].pageX) / 2 : getPointer(e).x,
598 startY: isPinch ? (e.touches[0].pageY + e.touches[1].pageY) / 2 : getPointer(e).y, 599 transX: getTransformX(image), 600 transY: getTransformY(image) 601 }); 602 zoomist.dragging = true; 603 zoomist.emit('dragStart', { 604 x: dragData.transX, 605 y: dragData.transY 606 }, e); 607 document.addEventListener(EVENT_TOUCH_MOVE, dragMove); 608 document.addEventListener(EVENT_TOUCH_END, dragEnd); 609 }; 610 611 const dragMove = e => { 612 if (!zoomist.dragging) return; 613 const isPinch = e.touches && e.touches.length === 2; 614 const pageX = isPinch ? (e.touches[0].pageX + e.touches[1].pageX) / 2 : getPointer(e).x; 615 const pageY = isPinch ? (e.touches[0].pageY + e.touches[1].pageY) / 2 : getPointer(e).y; 616 617 if (options.bounds) { 618 const minPageX = dragData.startX - (dragData.transX - imageData.left); 619 const maxPageX = dragData.startX - (dragData.transX + imageData.left); 620 const minPageY = dragData.startY - (dragData.transY - imageData.top); 621 const maxPageY = dragData.startY - (dragData.transY + imageData.top); 622 if (pageX < minPageX) dragData.startX += pageX - minPageX; 623 if (pageX > maxPageX) dragData.startX += pageX - maxPageX; 624 if (pageY < minPageY) dragData.startY += pageY - minPageY; 625 if (pageY > maxPageY) dragData.startY += pageY - maxPageY; 626 } 627 628 const transformX = roundToTwo(pageX - dragData.startX + dragData.transX); 629 const transformY = roundToTwo(pageY - dragData.startY + dragData.transY); 630 image.style.transform = `translate(${transformX}px, ${transformY}px)`; 631 zoomist.emit('drag', { 632 x: transformX, 633 y: transformY 634 }, e); 635 }; 636 637 const dragEnd = e => { 638 zoomist.dragging = false; 639 setObject(dragData, { 640 transX: getTransformX(image), 641 transY: getTransformY(image) 642 }); 643 zoomist.emit('dragEnd', { 644 x: dragData.transX, 645 y: dragData.transY 646 }, e); 647 document.removeEventListener(EVENT_TOUCH_MOVE, dragMove); 648 document.removeEventListener(EVENT_TOUCH_END, dragEnd); 649 }; // set image pinch event 650 651 652 zoomist.pinching = false; 653 zoomist.data.pinchData = { 654 dist: 0, 655 startX: 0, 656 startY: 0 657 }; 658 659 const pinchStart = e => { 660 if (!options.pinchable) return; 661 if (!e.touches || e.touches.length !== 2) return; 662 const { 663 pinchData 664 } = zoomist.data; 665 setObject(pinchData, { 666 dist: Math.hypot(e.touches[0].pageX - e.touches[1].pageX, e.touches[0].pageY - e.touches[1].pageY), 667 startX: e.touches[0].clientX, 668 startY: e.touches[0].clientY 669 }); // zoomist.dragging = false 670 671 zoomist.pinching = true; 672 zoomist.emit('pinchStart', e); 673 document.addEventListener(EVENT_TOUCH_MOVE, pinchMove); 674 document.addEventListener(EVENT_TOUCH_END, pinchEnd); 675 }; 676 677 const pinchMove = e => { 678 if (!zoomist.pinching) return; 679 const { 680 pinchData 681 } = zoomist.data; 682 const pinchDist = Math.hypot(e.touches[0].pageX - e.touches[1].pageX, e.touches[0].pageY - e.touches[1].pageY); 683 const zoomRatio = roundToTwo((pinchDist - pinchData.dist) / 100); 684 const pointer = { 685 clientX: (e.touches[0].clientX + e.touches[1].clientX) / 2, 686 clientY: (e.touches[0].clientY + e.touches[1].clientY) / 2 687 }; 688 zoomist.zoom(zoomRatio, pointer); 689 pinchData.dist = pinchDist; 690 zoomist.emit('pinch', e); 691 }; 692 693 const pinchEnd = e => { 694 zoomist.pinching = false; 695 zoomist.emit('pinchEnd', e); 696 document.removeEventListener(EVENT_TOUCH_MOVE, pinchMove); 697 document.removeEventListener(EVENT_TOUCH_END, pinchEnd); 698 }; // touch start handler 699 700 701 const touchStart = e => { 702 dragStart(e); 703 pinchStart(e); 704 }; 705 706 wrapper.addEventListener(EVENT_TOUCH_START, touchStart); // set zomm on mousewheel event 707 708 zoomist.wheeling = false; 709 710 const wheel = e => { 711 if (!options.wheelable) return; 712 e.preventDefault(); 713 const { 714 zoomRatio 715 } = options; 716 if (zoomist.wheeling) return; // prevent wheeling too fast 717 718 zoomist.wheeling = true; 719 setTimeout(() => { 720 zoomist.wheeling = false;
721 }, 30); 722 let delta; 723 if (e.deltaY) delta = e.deltaY > 0 ? -1 : 1;else if (e.wheelDelta) delta = e.wheelDelta / 120;else if (e.detail) delta = e.detail > 0 ? -1 : 1; 724 zoomist.zoom(delta * zoomRatio, getPointer(e)); 725 zoomist.emit('wheel', e); 726 }; 727 728 wrapper.addEventListener(EVENT_WHEEL, wheel); 729 }); // slider events 730 731 const sliderEvents = zoomist => { 732 const { 733 slider 734 } = zoomist.__modules__; // events 735 736 slider.sliding = false; 737 const isHorizontal = slider.direction === 'horizontal'; 738 739 const slideHandler = e => { 740 const rect = slider.sliderMain.getBoundingClientRect(); 741 const mousePoint = isHorizontal ? getPointer(e).clientX : -getPointer(e).clientY; 742 const sliderTotal = isHorizontal ? rect.width : rect.height; 743 const sliderStart = isHorizontal ? rect.left : -rect.bottom; 744 const percentage = minmax(roundToTwo((mousePoint - sliderStart) / sliderTotal), 0, 1); 745 const minRatio = zoomist.ratio < 1 ? zoomist.ratio : 1; 746 const maxRatio = zoomist.ratio > slider.maxRatio ? zoomist.ratio : slider.maxRatio; 747 const ratio = (maxRatio - minRatio) * percentage + minRatio; 748 zoomist.zoomTo(ratio); 749 }; 750 751 const slideStart = e => { 752 slideHandler(e); 753 slider.sliding = true; 754 zoomist.emit('slideStart', zoomist.getSliderValue(), e); 755 document.addEventListener(EVENT_TOUCH_MOVE, slideMove); 756 document.addEventListener(EVENT_TOUCH_END, slideEnd); 757 }; 758 759 const slideMove = e => { 760 if (!slider.sliding) return; 761 slideHandler(e); 762 zoomist.emit('slide', zoomist.getSliderValue(), e); 763 }; 764 765 const slideEnd = e => { 766 slider.sliding = false; 767 zoomist.emit('slideEnd', zoomist.getSliderValue(), e); 768 document.removeEventListener(EVENT_TOUCH_MOVE, slideMove); 769 document.removeEventListener(EVENT_TOUCH_END, slideEnd); 770 }; 771 772 slider.sliderMain.addEventListener(EVENT_TOUCH_START, slideStart); 773 slider.sliderMain.event = slideStart; 774 }; // zoomer events 775 776 const zoomerEvents = zoomist => { 777 const { 778 zoomRatio 779 } = zoomist.options; 780 const { 781 zoomer 782 } = zoomist.__modules__; 783 784 const zoomInHandler = () => zoomist.zoom(zoomRatio); 785 786 const zoomOutHandler = () => zoomist.zoom(-zoomRatio); 787 788 zoomer.zoomerInEl.addEventListener('click', zoomInHandler); 789 zoomer.zoomerOutEl.addEventListener('click', zoomOutHandler); 790 zoomer.zoomerInEl.event = zoomInHandler; 791 zoomer.zoomerOutEl.event = zoomOutHandler; 792 }; 793 794 const sliderTemp = ` 795 <div class="${CLASS_SLIDER_MAIN}"> 796 <span class="${CLASS_SLIDER_BAR}"></span> 797 <span class="${CLASS_SLIDER_BUTTON}"></span> 798 </div> 799`; 800 const inZoomerIcon = ` 801<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 12 12"> 802 <polygon points="12,5.5 6.5,5.5 6.5,0 5.5,0 5.5,5.5 0,5.5 0,6.5 5.5,6.5 5.5,12 6.5,12 6.5,6.5 12,6.5 "/> 803</svg> 804`; 805 const outZoomerIcon = ` 806<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 12 12"> 807 <rect y="5.5" width="12" height="1"/> 808</svg> 809`; 810 811 var MODULES = { 812 /** 813 * create all modules 814 */ 815 createModules() { 816 const { 817 options 818 } = this; 819 this.__modules__ = {}; 820 MODULES$1.forEach(module => { 821 if (options[module]) this[`create${upperFirstLetter(module)}`](); 822 }); 823 }, 824 825 /** 826 * create slider module 827 */ 828 createSlider() { 829 const { 830 element, 831 options, 832 __modules__ 833 } = this; 834 __modules__.slider = Object.assign({}, DEFAULT_SLIDER_OPTIONS, options.slider); 835 const { 836 slider 837 } = __modules__; 838 if (options.maxRatio) Object.assign(slider, { 839 maxRatio: options.maxRatio 840 }); 841 if (slider.direction !== 'horizontal' && slider.direction !== 'vertical') slider.direction = 'horizontal'; 842 slider.value = 0; // mount 843 844 if (slider.mounted) slider.sliderMain.remove(); 845 const isCustomEl = slider.isCustomEl = slider.el && isElementExist(slider.el); 846 const sliderEl = isCustomEl ? getElement(slider.el) : document.createElement('div'); 847 if (!isCustomEl) sliderEl.classList.add(CLASS_SLIDER); 848 sliderEl.innerHTML = sliderTemp; 849 slider.sliderEl = sliderEl; 850 slider.sliderMain = sliderEl.querySelector(`.${CLASS_SLIDER_MAIN}`); 851 slider.sliderBar = sliderEl.querySelector(`.${CLASS_SLIDER_BAR}`); 852 slider.sliderButton = sliderEl.querySelector(`.${CLASS_SLIDER_BUTTON}`); 853 slider.sliderMain.classList.add(`${CLASS_SLIDER}-${slider.direction}`); // events 854 855 sliderEvents(this); 856 slider.mounted = true; // render 857 858 if (!isCustomEl) element.append(sliderEl); 859 }, 860 861 /** 862 * destroy slider module 863 */ 864 destroySlider() { 865 const { 866 slider 867 } = this.__modules__; 868 if (!slider || !slider.mounted) return; 869 if (slider.isCustomEl) slider.sliderMain.remove();else slider.sliderEl.remove(); 870 slider.mounted = false;
871 }, 872 873 /** 874 * create zoomer module 875 */ 876 createZoomer() { 877 const { 878 element, 879 options, 880 __modules__ 881 } = this; 882 __modules__.zoomer = Object.assign({}, DEFAULT_ZOOMER_OPTIONS, options.zoomer); 883 const { 884 zoomer 885 } = __modules__; // mount 886 887 if (zoomer.mounted && zoomer.zoomerEl) zoomer.sliderMain.remove(); 888 const isCustomInEl = zoomer.isCustomInEl = zoomer.inEl && isElementExist(zoomer.inEl); 889 const isCustomOutEl = zoomer.isCustomOutEl = zoomer.outEl && isElementExist(zoomer.outEl); 890 //£ ACCESSIBILITE transformation des div en bouton 891 const zoomerInEl = isCustomInEl ? getElement(zoomer.inEl) : document.createElement('button'); 892 const zoomerOutEl = isCustomOutEl ? getElement(zoomer.outEl) : document.createElement('button'); 893 894 //£ ACCESSIBILITE ajout d'un titre aux boutons 895 zoomerInEl.setAttribute("title", "Zoomer sur la carte") 896 zoomerOutEl.setAttribute("title", "Dézoomer sur la carte") 897 898 if (!isCustomInEl) { 899 zoomerInEl.classList.add(CLASS_ZOOMER_IN); 900 zoomerInEl.innerHTML = inZoomerIcon; 901 } 902 903 if (!isCustomOutEl) { 904 zoomerOutEl.classList.add(CLASS_ZOOMER_OUT); 905 zoomerOutEl.innerHTML = outZoomerIcon; 906 } 907 908 zoomer.zoomerInEl = zoomerInEl; 909 zoomer.zoomerOutEl = zoomerOutEl; 910 911 if (zoomer.disableOnBounds) { 912 const { 913 bounds, 914 maxRatio 915 } = options; 916 if (bounds && this.ratio === 1) zoomerOutEl.classList.add(CLASS_ZOOMER_DISABLE); 917 if (this.ratio === maxRatio) zoomerInEl.classList.add(CLASS_ZOOMER_DISABLE); 918 } // events 919 920 921 zoomerEvents(this); 922 zoomer.mounted = true; // render 923 924 if (!isCustomInEl || !isCustomOutEl) { 925 const zoomerEl = document.createElement('div'); 926 zoomerEl.classList.add(CLASS_ZOOMER); 927 if (!isCustomInEl) zoomerEl.append(zoomerInEl); 928 if (!isCustomOutEl) zoomerEl.append(zoomerOutEl); 929 zoomer.zoomerEl = zoomerEl; 930 element.append(zoomerEl); 931 } 932 }, 933 934 /** 935 * destroy zoomer module 936 */ 937 destroyZoomer() { 938 const { 939 zoomer 940 } = this.__modules__; 941 if (!zoomer || !zoomer.mounted) return; 942 943 const unbindZoomer = target => { 944 target.removeEventListener('click', target.event); 945 target.event = undefined; 946 target.classList.remove(CLASS_ZOOMER_DISABLE); 947 }; 948 949 if (zoomer.isCustomInEl) unbindZoomer(zoomer.zoomerInEl);else zoomer.zoomerInEl.remove(); 950 if (zoomer.isCustomOutEl) unbindZoomer(zoomer.zoomerOutEl);else zoomer.zoomerOutEl.remove(); 951 if (zoomer.zoomerEl) zoomer.zoomerEl.remove(); 952 zoomer.mounted = false; 953 } 954 955 }; 956 957 class Zoomist { 958 /** 959 * 960 * @param {Element} element - target element 961 * @param {Object} options - the configuration options 962 */ 963 constructor(element, options = {}) { 964 if (!element) throw new Error('The first argument is required.'); 965 if (!isElementExist(element)) throw new Error('This element is not exist.'); 966 this.element = getElement(element); 967 this.options = Object.assign({}, DEFAULT_OPTIONS, isPlainObject(options) && options); 968 this.init(); 969 } 970 971 init() { 972 const { 973 element, 974 options 975 } = this; 976 const { 977 src 978 } = options; 979 if (element[NAME]) return; 980 element[NAME] = this; 981 const source = options.src = isString(src) || isImg(src) ? src : DEFAULT_OPTIONS.src; 982 const url = isImg(source) ? source.src : element.getAttribute(source); 983 if (!url) throw new Error(`Cannot find src from ${source}`); 984 this.create(url); 985 } 986 987 create(url) { 988 if (!url) return; 989 const { 990 options 991 } = this; 992 this.url = url; 993 this.data = {}; 994 this.ratio = 1; 995 this.__events__ = EVENTS; 996 997 if (options.on) { 998 for (const [k, v] of Object.entries(options.on)) { 999 this.__events__[k] = [v]; 1000 } 1001 } 1002 1003 this.mount(); 1004 } 1005 1006 mount() { 1007 if (this.mounted) return; 1008 const { 1009 element, 1010 options, 1011 data, 1012 url 1013 } = this; 1014 const { 1015 fill, 1016 maxRatio, 1017 height 1018 } = options; 1019 if (this.wrapper) this.wrapper.remove(); 1020 const wrapper = document.createElement('div'); 1021 wrapper.classList.add(CLASS_WRAPPER); 1022 const image = document.createElement('img'); 1023 image.classList.add(CLASS_IMAGE); 1024 image.src = url; 1025 //£ ACCESSIBILITE récupération de l'attribut data-zoomist-alt pour en faire l'alt de l'img carte 1026 //£ ACCESSIBILITE TODO: long desc ? 1027 image.alt = element.dataset.zoomistAlt 1028 1029 image.onload = () => { 1030 this.wrapper = wrapper; 1031 this.image = image; 1032 const {
1033 naturalWidth, 1034 naturalHeight 1035 } = image; 1036 const imageRatio = naturalWidth / naturalHeight; // set container height 1037 1038 if (height) { 1039 setStyle(element, { 1040 width: '100%' 1041 }); 1042 if (height === 'auto') setStyle(element, { 1043 paddingBottom: `${roundToTwo(naturalHeight / naturalWidth * 100)}%` 1044 });else if (isNumber(height)) setStyle(element, { 1045 height: height 1046 });else if (isPercentage(height)) setStyle(element, { 1047 paddingBottom: height 1048 }); 1049 } 1050 1051 const { 1052 offsetWidth, 1053 offsetHeight 1054 } = element; 1055 this.data.containerData = { 1056 width: offsetWidth, 1057 height: offsetHeight, 1058 aspectRatio: offsetWidth / offsetHeight 1059 }; // get base on width or height 1060 1061 const { 1062 containerData 1063 } = data; 1064 let baseSide; 1065 if (fill !== 'cover' && fill !== 'contain' && fill !== 'none') options.fill = 'cover'; 1066 if (options.fill !== 'contain') baseSide = containerData.aspectRatio === imageRatio ? 'both' : containerData.aspectRatio > imageRatio ? 'width' : 'height'; 1067 if (options.fill === 'contain') baseSide = containerData.aspectRatio === imageRatio ? 'both' : containerData.aspectRatio > imageRatio ? 'height' : 'width'; // calculate the image width, height, left, top 1068 1069 const imageWidth = options.fill === 'none' ? naturalWidth : baseSide === 'both' || baseSide === 'width' ? containerData.width : containerData.height * imageRatio; 1070 const imageHeight = options.fill === 'none' ? naturalHeight : baseSide === 'both' || baseSide === 'height' ? containerData.height : containerData.width / imageRatio; 1071 const imageLeft = (containerData.width - imageWidth) / 2; 1072 const imageTop = (containerData.height - imageHeight) / 2; 1073 this.data.originImageData = { 1074 naturalWidth, 1075 naturalHeight, 1076 aspectRatio: imageRatio, 1077 width: imageWidth, 1078 height: imageHeight, 1079 left: imageLeft, 1080 top: imageTop 1081 }; 1082 this.data.imageData = Object.assign({}, this.data.originImageData); 1083 setStyle(image, { 1084 width: imageWidth, 1085 height: imageHeight, 1086 left: imageLeft, 1087 top: imageTop 1088 }); // if has maxRatio 1089 1090 if ((!isNumber(maxRatio) || maxRatio <= 1) && maxRatio !== false) options.maxRatio = false; 1091 bindEvents(this); 1092 this.mounted = true; 1093 this.render(); 1094 }; 1095 } 1096 1097 render() { 1098 const { 1099 element, 1100 wrapper, 1101 image 1102 } = this; 1103 element.classList.add(CLASS_CONTAINER); 1104 wrapper.append(image); 1105 element.append(wrapper); 1106 this.createModules(); 1107 this.emit('ready'); 1108 } 1109 1110 } 1111 1112 Object.assign(Zoomist.prototype, METHODS, MODULES); 1113 1114 return Zoomist; 1115 1116}));
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.