@mozert-studio/mozert-slider 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/dist/mozert-theme.css +16 -16
- package/dist/mozert-theme.min.css +2 -2
- package/dist/mozert.core.js +41 -12
- package/dist/mozert.core.min.js +2 -2
- package/dist/mozert.css +59 -1
- package/dist/mozert.esm.js +570 -16
- package/dist/mozert.esm.min.js +2 -2
- package/dist/mozert.esm.mjs +570 -16
- package/dist/mozert.jquery.js +1 -1
- package/dist/mozert.jquery.min.js +1 -1
- package/dist/mozert.js +570 -16
- package/dist/mozert.min.css +2 -2
- package/dist/mozert.min.js +2 -2
- package/package.json +62 -11
- package/types/mozert.d.ts +13 -2
package/dist/mozert.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Mozert 1.1.
|
|
2
|
+
* Mozert 1.1.1
|
|
3
3
|
* A dependency-free, responsive, touch-friendly carousel / slider.
|
|
4
4
|
* MIT License
|
|
5
5
|
*/
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
}(typeof self !== 'undefined' ? self : this, function () {
|
|
11
11
|
'use strict';
|
|
12
12
|
//#region core
|
|
13
|
-
const VERSION = '1.1.
|
|
13
|
+
const VERSION = '1.1.1';
|
|
14
14
|
|
|
15
15
|
const DEFAULTS = {
|
|
16
16
|
accessibility: true,
|
|
@@ -76,7 +76,7 @@ const DEFAULTS = {
|
|
|
76
76
|
on: null,
|
|
77
77
|
};
|
|
78
78
|
|
|
79
|
-
const LABELS = { slide: 'Slide {n} of {total}', prev: 'Previous slide', next: 'Next slide', goTo: 'Go to slide {n}' };
|
|
79
|
+
const LABELS = { slide: 'Slide {n} of {total}', prev: 'Previous slide', next: 'Next slide', goTo: 'Go to slide {n}', fullscreen: 'Toggle fullscreen', close: 'Close', zoom: 'Zoom', lightbox: 'Image viewer', imageOf: 'Image {n} of {total}', magnifier: 'Magnifier' };
|
|
80
80
|
const DEFAULT_PREV = '<button type="button" class="mozert-prev" aria-label="Previous slide">Previous</button>';
|
|
81
81
|
const DEFAULT_NEXT = '<button type="button" class="mozert-next" aria-label="Next slide">Next</button>';
|
|
82
82
|
const FOCUSABLE = 'a[href],button,input,select,textarea,[tabindex],[contenteditable="true"]';
|
|
@@ -469,7 +469,7 @@ class Mozert {
|
|
|
469
469
|
s.slidesToShow = 1;
|
|
470
470
|
s.slidesToScroll = 1;
|
|
471
471
|
}
|
|
472
|
-
if (s.effect === 'coverflow') s.centerMode = true;
|
|
472
|
+
if (s.effect === 'coverflow' || s.effect === 'expand') s.centerMode = true;
|
|
473
473
|
if (s.freeMode || s.virtual || s.bleed) s.infinite = false;
|
|
474
474
|
if (s.marquee) {
|
|
475
475
|
s.infinite = true;
|
|
@@ -1046,7 +1046,18 @@ class Mozert {
|
|
|
1046
1046
|
|
|
1047
1047
|
_slideTo(target, opts = {}) {
|
|
1048
1048
|
const s = this.s;
|
|
1049
|
-
if (!this.active
|
|
1049
|
+
if (!this.active) return false;
|
|
1050
|
+
if (!this.canNav) {
|
|
1051
|
+
// A strip that shows every slide cannot move, but a synced partner can still change which slide is highlighted.
|
|
1052
|
+
if (opts.fromSync) {
|
|
1053
|
+
const sel = opts.sel !== undefined && opts.sel !== 0 ? opts.sel : null;
|
|
1054
|
+
if (sel !== this._sel) {
|
|
1055
|
+
this._sel = sel;
|
|
1056
|
+
this._updateSlideClasses(this._logical);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
return false;
|
|
1060
|
+
}
|
|
1050
1061
|
if (this.animating && !opts.fromSync && s.waitForAnimate) return false;
|
|
1051
1062
|
let next = target;
|
|
1052
1063
|
if (!s.infinite || s.stacked) next = s.stacked ? this._norm(next) : clamp(next, 0, this.maxIndex);
|
|
@@ -1055,9 +1066,19 @@ class Mozert {
|
|
|
1055
1066
|
// (a thumbnail strip stops at its last full page, but the main slider should still go to the clicked one).
|
|
1056
1067
|
const want = s.infinite || s.stacked ? real : clamp(target, 0, this.n - 1);
|
|
1057
1068
|
if (next === this.current && !this.animating && !opts.force) {
|
|
1058
|
-
if (
|
|
1059
|
-
|
|
1060
|
-
|
|
1069
|
+
if (opts.fromSync) {
|
|
1070
|
+
// Already at the clamped position: only move the highlight to the slide the partner asked for.
|
|
1071
|
+
const sel = opts.sel !== undefined && opts.sel !== next ? opts.sel : null;
|
|
1072
|
+
if (sel !== this._sel) {
|
|
1073
|
+
this._sel = sel;
|
|
1074
|
+
this._updateSlideClasses(this._logical);
|
|
1075
|
+
}
|
|
1076
|
+
} else {
|
|
1077
|
+
// The strip already sits on this index (its last full page), but the partner may still be elsewhere.
|
|
1078
|
+
if (want !== next) {
|
|
1079
|
+
this._sel = want;
|
|
1080
|
+
this._updateSlideClasses(this._logical);
|
|
1081
|
+
}
|
|
1061
1082
|
this._syncNav(want);
|
|
1062
1083
|
}
|
|
1063
1084
|
return false;
|
|
@@ -1319,11 +1340,19 @@ class Mozert {
|
|
|
1319
1340
|
this._dragged = false;
|
|
1320
1341
|
return;
|
|
1321
1342
|
}
|
|
1322
|
-
if (s.focusOnSelect && this.canNav) {
|
|
1343
|
+
if (s.focusOnSelect && (this.canNav || s.asNavFor)) {
|
|
1323
1344
|
const slide = e.target.closest ? e.target.closest('.mozert-slide') : null;
|
|
1324
1345
|
if (slide && this.track.contains(slide)) {
|
|
1325
1346
|
const i = parseInt(slide.getAttribute('data-mozert-index'), 10);
|
|
1326
|
-
if (!isNaN(i))
|
|
1347
|
+
if (!isNaN(i)) {
|
|
1348
|
+
if (this.canNav) this.goTo(i);
|
|
1349
|
+
else {
|
|
1350
|
+
// Nothing to scroll: just highlight the clicked slide and tell the partner.
|
|
1351
|
+
this._sel = i;
|
|
1352
|
+
this._updateSlideClasses(this._logical);
|
|
1353
|
+
this._syncNav(i);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1327
1356
|
}
|
|
1328
1357
|
}
|
|
1329
1358
|
},
|
|
@@ -1564,11 +1593,11 @@ class Mozert {
|
|
|
1564
1593
|
const v = this.s.asNavFor;
|
|
1565
1594
|
if (!v) return [];
|
|
1566
1595
|
let els = [];
|
|
1567
|
-
if (v instanceof Mozert) return [v];
|
|
1596
|
+
if (v instanceof Mozert) return v.active && v !== this ? [v] : [];
|
|
1568
1597
|
if (typeof v === 'string') els = Array.from(document.querySelectorAll(v));
|
|
1569
1598
|
else if (isEl(v)) els = [v];
|
|
1570
1599
|
else if (v.length) els = Array.from(v);
|
|
1571
|
-
return els.map((e) => registry.get(e)).filter((i) => i && i !== this && i.active
|
|
1600
|
+
return els.map((e) => registry.get(e)).filter((i) => i && i !== this && i.active);
|
|
1572
1601
|
}
|
|
1573
1602
|
|
|
1574
1603
|
_syncNav(index) {
|
|
@@ -1745,11 +1774,12 @@ Mozert._bridge = null;
|
|
|
1745
1774
|
const pct = (v, size) => (typeof v === 'string' && v.indexOf('%') > -1 ? (parseFloat(v) / 100) * size : parseFloat(v) || 0);
|
|
1746
1775
|
|
|
1747
1776
|
/* ------------------------------------------------------------ effects */
|
|
1748
|
-
const FX = ['coverflow', 'cube', 'flip', 'cards', 'creative'];
|
|
1777
|
+
const FX = ['coverflow', 'cube', 'flip', 'cards', 'creative', 'expand'];
|
|
1749
1778
|
Mozert.use({
|
|
1750
1779
|
name: 'effects',
|
|
1751
1780
|
defaults: {
|
|
1752
|
-
coverflowEffect: { rotate: 45, depth: 120, scale: 1, stretch: 0, modifier: 1, shadow: true },
|
|
1781
|
+
coverflowEffect: { rotate: 45, depth: 120, scale: 1, stretch: 0, modifier: 1, lift: 0, fade: 0, shadow: true },
|
|
1782
|
+
expandEffect: { extra: 1.4 },
|
|
1753
1783
|
cubeEffect: { shadow: true, slideShadows: true, perspective: 1200 },
|
|
1754
1784
|
flipEffect: { perspective: 1400, limitRotation: true },
|
|
1755
1785
|
cardsEffect: { perSlideOffset: 10, perSlideRotate: 2, rotate: true, visible: 4 },
|
|
@@ -1800,8 +1830,22 @@ Mozert._bridge = null;
|
|
|
1800
1830
|
const rot = clampN(-r, -3, 3) * o.rotate * o.modifier;
|
|
1801
1831
|
const tz = -a * o.depth;
|
|
1802
1832
|
const sc = 1 - Math.min(a, 3) * (1 - o.scale) * 0.5;
|
|
1803
|
-
tr = 'translate3d(' + -r * o.stretch + 'px,
|
|
1804
|
-
op = a > 4 ? 0 : 1;
|
|
1833
|
+
tr = 'translate3d(' + -r * o.stretch + 'px,' + -Math.min(a, 3) * o.lift + 'px,' + tz + 'px) rotateY(' + rot + 'deg) scale(' + sc + ')';
|
|
1834
|
+
op = a > 4 ? 0 : Math.max(0, 1 - Math.min(a, 4) * o.fade);
|
|
1835
|
+
} else if (fx === 'expand') {
|
|
1836
|
+
// The active card grows wider, its neighbours slide out of the way.
|
|
1837
|
+
// Both follow the same linear rule, so dragging stays continuous.
|
|
1838
|
+
const m = Math.min(1, a);
|
|
1839
|
+
const w0 = e.offsetWidth;
|
|
1840
|
+
const grow = o.extra * w0 * (1 - m);
|
|
1841
|
+
tr = 'translate3d(' + (r < 0 ? -1 : 1) * ((o.extra * w0) / 2) * m + 'px,0,0)';
|
|
1842
|
+
const card = e.firstElementChild;
|
|
1843
|
+
if (card) {
|
|
1844
|
+
card.style.transition = animate && !jump ? 'width ' + speed + 'ms ' + s.cssEase + ', margin-left ' + speed + 'ms ' + s.cssEase : 'none';
|
|
1845
|
+
card.style.width = w0 + grow + 'px';
|
|
1846
|
+
card.style.marginLeft = -grow / 2 + 'px';
|
|
1847
|
+
card.style.maxWidth = 'none';
|
|
1848
|
+
}
|
|
1805
1849
|
} else if (fx === 'cube') {
|
|
1806
1850
|
tr = 'translate3d(0,0,' + -W / 2 + 'px) rotateY(' + r * 90 + 'deg) translate3d(0,0,' + W / 2 + 'px)';
|
|
1807
1851
|
op = a <= 1.01 ? 1 : 0;
|
|
@@ -1848,6 +1892,7 @@ Mozert._bridge = null;
|
|
|
1848
1892
|
list.style.overflow = '';
|
|
1849
1893
|
sl.el.classList.remove('mozert-fx-' + fx);
|
|
1850
1894
|
slides().forEach((e) => {
|
|
1895
|
+
if (fx === 'expand' && e.firstElementChild) e.firstElementChild.style.width = e.firstElementChild.style.marginLeft = e.firstElementChild.style.transition = e.firstElementChild.style.maxWidth = '';
|
|
1851
1896
|
e.style.transform = e.style.opacity = e.style.zIndex = e.style.transition = e.style.pointerEvents = e.style.backfaceVisibility = '';
|
|
1852
1897
|
});
|
|
1853
1898
|
},
|
|
@@ -2612,6 +2657,515 @@ Mozert._bridge = null;
|
|
|
2612
2657
|
};
|
|
2613
2658
|
},
|
|
2614
2659
|
});
|
|
2660
|
+
|
|
2661
|
+
/* ------------------------------------------------------------- thumbs */
|
|
2662
|
+
// thumbs: '#nav' (an existing strip), true (create one below the slider) or { el, options, count }.
|
|
2663
|
+
// An empty strip is filled automatically from each slide's data-thumb, first <img> or background.
|
|
2664
|
+
Mozert.use({
|
|
2665
|
+
name: 'thumbs',
|
|
2666
|
+
defaults: { thumbs: false },
|
|
2667
|
+
create(sl) {
|
|
2668
|
+
const cfg = sl.s.thumbs;
|
|
2669
|
+
if (!cfg || sl.n < 2) return null;
|
|
2670
|
+
const o = objOpt(cfg, { el: null, options: {}, count: 5, position: 'bottom', breakpoint: 640 });
|
|
2671
|
+
const want = ['left', 'right', 'top', 'bottom'].indexOf(o.position) > -1 ? o.position : 'bottom';
|
|
2672
|
+
// A side strip moves below the picture on narrow screens (viewport <= breakpoint px).
|
|
2673
|
+
const mq = (want === 'left' || want === 'right') && o.breakpoint > 0 && typeof matchMedia === 'function' ? matchMedia('(max-width:' + o.breakpoint + 'px)') : null;
|
|
2674
|
+
const pos = mq && mq.matches ? 'bottom' : want;
|
|
2675
|
+
const side = pos === 'left' || pos === 'right';
|
|
2676
|
+
const onMq = () => sl.active && sl.refresh();
|
|
2677
|
+
if (mq) mq.addEventListener ? mq.addEventListener('change', onMq) : mq.addListener(onMq);
|
|
2678
|
+
let host = null;
|
|
2679
|
+
let made = false;
|
|
2680
|
+
let wrap = null;
|
|
2681
|
+
const ref = cfg === true ? null : typeof cfg === 'object' && !cfg.nodeType ? o.el : cfg;
|
|
2682
|
+
if (ref) host = typeof ref === 'string' ? document.querySelector(ref) : ref && ref.nodeType ? ref : null;
|
|
2683
|
+
if (!host) {
|
|
2684
|
+
if (ref) return null;
|
|
2685
|
+
host = mk('div', 'mozert-thumbs');
|
|
2686
|
+
made = true;
|
|
2687
|
+
if (side) {
|
|
2688
|
+
wrap = mk('div', 'mozert-gallery mozert-gallery-' + pos);
|
|
2689
|
+
sl.el.parentNode.insertBefore(wrap, sl.el);
|
|
2690
|
+
wrap.appendChild(sl.el);
|
|
2691
|
+
wrap.appendChild(host);
|
|
2692
|
+
} else if (pos === 'top') sl.el.parentNode.insertBefore(host, sl.el);
|
|
2693
|
+
else sl.el.parentNode.insertBefore(host, sl.el.nextSibling);
|
|
2694
|
+
}
|
|
2695
|
+
host.setAttribute('data-mozert-thumbs', pos);
|
|
2696
|
+
const auto = host.children.length === 0;
|
|
2697
|
+
if (auto) {
|
|
2698
|
+
(sl._items || []).forEach((src, i) => {
|
|
2699
|
+
const cell = mk('div');
|
|
2700
|
+
const first = src.firstElementChild;
|
|
2701
|
+
const img = src.querySelector('img');
|
|
2702
|
+
const url = src.getAttribute('data-thumb') || (img && (img.getAttribute('data-thumb') || img.getAttribute('src') || img.getAttribute('data-lazy')));
|
|
2703
|
+
if (url) {
|
|
2704
|
+
const t = mk('img', 'mozert-thumb');
|
|
2705
|
+
t.src = url;
|
|
2706
|
+
t.alt = '';
|
|
2707
|
+
t.draggable = false;
|
|
2708
|
+
cell.appendChild(t);
|
|
2709
|
+
} else {
|
|
2710
|
+
const t = mk('div', 'mozert-thumb mozert-thumb-fill');
|
|
2711
|
+
const bg = first ? getComputedStyle(first).backgroundImage : 'none';
|
|
2712
|
+
if (bg && bg !== 'none') t.style.backgroundImage = bg;
|
|
2713
|
+
else t.style.background = (first && getComputedStyle(first).backgroundColor) || '#ccd';
|
|
2714
|
+
t.textContent = String(i + 1);
|
|
2715
|
+
cell.appendChild(t);
|
|
2716
|
+
}
|
|
2717
|
+
host.appendChild(cell);
|
|
2718
|
+
});
|
|
2719
|
+
}
|
|
2720
|
+
const count = Math.max(1, Math.min(o.count, sl.n));
|
|
2721
|
+
const gap = o.options && o.options.spaceBetween != null ? o.options.spaceBetween : 10;
|
|
2722
|
+
const fit = () => {
|
|
2723
|
+
// vertical strip: size each thumb so `count` of them fill exactly the height of the main slider
|
|
2724
|
+
const H = sl.el.offsetHeight;
|
|
2725
|
+
if (!H) return;
|
|
2726
|
+
host.style.setProperty('--mozert-thumb-h', Math.max(24, (H - gap * (count - 1)) / count) + 'px');
|
|
2727
|
+
host.style.height = H + 'px';
|
|
2728
|
+
th.refresh();
|
|
2729
|
+
};
|
|
2730
|
+
const th = new Mozert(host, Object.assign({ slidesToShow: count, slidesToScroll: 1, arrows: false, dots: false, infinite: false, swipeToSlide: true, spaceBetween: gap, focusOnSelect: true, initialSlide: 0, vertical: side }, o.options, { asNavFor: sl, thumbs: false, lightbox: false }));
|
|
2731
|
+
let ro = null;
|
|
2732
|
+
if (side) {
|
|
2733
|
+
fit();
|
|
2734
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
2735
|
+
let lastH = sl.el.offsetHeight;
|
|
2736
|
+
ro = new ResizeObserver(() => {
|
|
2737
|
+
if (sl.el.offsetHeight !== lastH) {
|
|
2738
|
+
lastH = sl.el.offsetHeight;
|
|
2739
|
+
fit();
|
|
2740
|
+
}
|
|
2741
|
+
});
|
|
2742
|
+
ro.observe(sl.el);
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
const prevNav = sl.s.asNavFor;
|
|
2746
|
+
if (!prevNav) sl.s.asNavFor = th;
|
|
2747
|
+
host.classList.add('mozert-thumbs');
|
|
2748
|
+
sl._syncNav(sl.current);
|
|
2749
|
+
return {
|
|
2750
|
+
destroy() {
|
|
2751
|
+
if (sl.s.asNavFor === th) sl.s.asNavFor = prevNav;
|
|
2752
|
+
if (ro) ro.disconnect();
|
|
2753
|
+
if (mq) mq.removeEventListener ? mq.removeEventListener('change', onMq) : mq.removeListener(onMq);
|
|
2754
|
+
host.removeAttribute('data-mozert-thumbs');
|
|
2755
|
+
host.style.removeProperty('--mozert-thumb-h');
|
|
2756
|
+
if (side) host.style.height = '';
|
|
2757
|
+
th.destroy();
|
|
2758
|
+
if (wrap && wrap.parentNode) {
|
|
2759
|
+
wrap.parentNode.insertBefore(sl.el, wrap);
|
|
2760
|
+
wrap.parentNode.removeChild(wrap);
|
|
2761
|
+
} else if (made) host.parentNode && host.parentNode.removeChild(host);
|
|
2762
|
+
else if (auto) host.innerHTML = '';
|
|
2763
|
+
},
|
|
2764
|
+
};
|
|
2765
|
+
},
|
|
2766
|
+
});
|
|
2767
|
+
|
|
2768
|
+
/* ----------------------------------------------------------- lightbox */
|
|
2769
|
+
// lightbox: true | { zoom: true, loop: true }. Click a slide to open it full screen.
|
|
2770
|
+
Mozert.use({
|
|
2771
|
+
name: 'lightbox',
|
|
2772
|
+
defaults: { lightbox: false },
|
|
2773
|
+
create(sl) {
|
|
2774
|
+
if (!sl.s.lightbox) return null;
|
|
2775
|
+
const o = objOpt(sl.s.lightbox, { zoom: true, loop: true, maxZoom: 2.5 });
|
|
2776
|
+
let box = null;
|
|
2777
|
+
let idx = 0;
|
|
2778
|
+
let opener = null;
|
|
2779
|
+
let zoomed = false;
|
|
2780
|
+
let zrect = null;
|
|
2781
|
+
let drag = null;
|
|
2782
|
+
const items = () => sl._items || [];
|
|
2783
|
+
const isImg = (h) => /^data:image\//i.test(h || '') || /\.(jpe?g|png|gif|webp|avif|svg)(\?|#|$)/i.test(h || '');
|
|
2784
|
+
const info = (src) => {
|
|
2785
|
+
const img = src.querySelector('img');
|
|
2786
|
+
const a = src.querySelector('a[href]');
|
|
2787
|
+
const full = src.getAttribute('data-full') || (img && img.getAttribute('data-full')) || (a && isImg(a.getAttribute('href')) ? a.getAttribute('href') : '') || (img && (img.currentSrc || img.getAttribute('src'))) || '';
|
|
2788
|
+
return { src: full, alt: (img && img.getAttribute('alt')) || '', cap: src.getAttribute('data-caption') || (img && img.getAttribute('data-caption')) || '', node: src };
|
|
2789
|
+
};
|
|
2790
|
+
const stageContent = (it) => {
|
|
2791
|
+
if (it.src) {
|
|
2792
|
+
const im = mk('img', 'mozert-lb-img');
|
|
2793
|
+
im.src = it.src;
|
|
2794
|
+
im.alt = it.alt;
|
|
2795
|
+
im.draggable = false;
|
|
2796
|
+
return im;
|
|
2797
|
+
}
|
|
2798
|
+
// Slides without an image: show a larger copy of the slide itself.
|
|
2799
|
+
const w = mk('div', 'mozert-lb-clone');
|
|
2800
|
+
w.innerHTML = it.node.innerHTML;
|
|
2801
|
+
return w;
|
|
2802
|
+
};
|
|
2803
|
+
const setZoom = (on, ev) => {
|
|
2804
|
+
const el = box && box.querySelector('.mozert-lb-img,.mozert-lb-clone');
|
|
2805
|
+
if (!el || !o.zoom) return;
|
|
2806
|
+
zoomed = on;
|
|
2807
|
+
box.classList.toggle('mozert-lb-zoomed', on);
|
|
2808
|
+
if (on) {
|
|
2809
|
+
const r = el.getBoundingClientRect();
|
|
2810
|
+
zrect = r;
|
|
2811
|
+
const ox = ev ? ((ev.clientX - r.left) / r.width) * 100 : 50;
|
|
2812
|
+
const oy = ev ? ((ev.clientY - r.top) / r.height) * 100 : 50;
|
|
2813
|
+
el.style.transformOrigin = ox + '% ' + oy + '%';
|
|
2814
|
+
el.style.transform = 'scale(' + o.maxZoom + ')';
|
|
2815
|
+
} else {
|
|
2816
|
+
el.style.transform = '';
|
|
2817
|
+
}
|
|
2818
|
+
};
|
|
2819
|
+
const show = (i, dir) => {
|
|
2820
|
+
const list = items();
|
|
2821
|
+
if (!list.length) return;
|
|
2822
|
+
if (o.loop) i = (i + list.length) % list.length;
|
|
2823
|
+
else i = clampN(i, 0, list.length - 1);
|
|
2824
|
+
idx = i;
|
|
2825
|
+
setZoom(false);
|
|
2826
|
+
const it = info(list[i]);
|
|
2827
|
+
const stage = box.querySelector('.mozert-lb-stage');
|
|
2828
|
+
stage.innerHTML = '';
|
|
2829
|
+
const c = stageContent(it);
|
|
2830
|
+
stage.appendChild(c);
|
|
2831
|
+
c.classList.add('mozert-lb-in');
|
|
2832
|
+
if (dir) c.style.setProperty('--lb-from', dir > 0 ? '40px' : '-40px');
|
|
2833
|
+
box.querySelector('.mozert-lb-count').textContent = i + 1 + ' / ' + list.length;
|
|
2834
|
+
box.querySelector('.mozert-lb-cap').textContent = it.cap;
|
|
2835
|
+
box.setAttribute('aria-label', sl._label('imageOf', { n: i + 1, total: list.length }));
|
|
2836
|
+
const edge = !o.loop && list.length > 1;
|
|
2837
|
+
box.querySelector('.mozert-lb-prev').disabled = edge && i === 0;
|
|
2838
|
+
box.querySelector('.mozert-lb-next').disabled = edge && i === list.length - 1;
|
|
2839
|
+
// Preload the neighbours.
|
|
2840
|
+
[i - 1, i + 1].forEach((k) => {
|
|
2841
|
+
const n = list[(k + list.length) % list.length];
|
|
2842
|
+
const u = n && info(n).src;
|
|
2843
|
+
if (u) new Image().src = u;
|
|
2844
|
+
});
|
|
2845
|
+
if (sl.emit) sl.emit('lightboxChange', i);
|
|
2846
|
+
};
|
|
2847
|
+
const key = (e) => {
|
|
2848
|
+
if (!box) return;
|
|
2849
|
+
if (e.key === 'Escape') {
|
|
2850
|
+
e.preventDefault();
|
|
2851
|
+
close();
|
|
2852
|
+
} else if (e.key === 'ArrowRight') show(idx + 1, 1);
|
|
2853
|
+
else if (e.key === 'ArrowLeft') show(idx - 1, -1);
|
|
2854
|
+
else if (e.key === 'Tab') {
|
|
2855
|
+
const f = Array.from(box.querySelectorAll('button:not([disabled])'));
|
|
2856
|
+
if (!f.length) return;
|
|
2857
|
+
const first = f[0];
|
|
2858
|
+
const last = f[f.length - 1];
|
|
2859
|
+
if (e.shiftKey && document.activeElement === first) {
|
|
2860
|
+
e.preventDefault();
|
|
2861
|
+
last.focus();
|
|
2862
|
+
} else if (!e.shiftKey && document.activeElement === last) {
|
|
2863
|
+
e.preventDefault();
|
|
2864
|
+
first.focus();
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
};
|
|
2868
|
+
const open = (i) => {
|
|
2869
|
+
if (box || !items().length) return;
|
|
2870
|
+
opener = document.activeElement;
|
|
2871
|
+
const lb = (k) => sl._label(k);
|
|
2872
|
+
const single = items().length < 2;
|
|
2873
|
+
box = mk('div', 'mozert-lb');
|
|
2874
|
+
box.setAttribute('role', 'dialog');
|
|
2875
|
+
box.setAttribute('aria-modal', 'true');
|
|
2876
|
+
box.innerHTML =
|
|
2877
|
+
'<div class="mozert-lb-stage"></div>' +
|
|
2878
|
+
'<div class="mozert-lb-bar"><span class="mozert-lb-count"></span>' +
|
|
2879
|
+
(o.zoom ? '<button type="button" class="mozert-lb-zoom" aria-label="' + lb('zoom') + '"><svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3M11 8v6M8 11h6"/></svg></button>' : '') +
|
|
2880
|
+
'<button type="button" class="mozert-lb-close" aria-label="' + lb('close') + '"><svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M5 5l14 14M19 5L5 19"/></svg></button></div>' +
|
|
2881
|
+
(single ? '' : '<button type="button" class="mozert-lb-prev" aria-label="' + lb('prev') + '"><svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2.4" aria-hidden="true"><path d="M15 5l-7 7 7 7"/></svg></button><button type="button" class="mozert-lb-next" aria-label="' + lb('next') + '"><svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2.4" aria-hidden="true"><path d="M9 5l7 7-7 7"/></svg></button>') +
|
|
2882
|
+
'<div class="mozert-lb-cap"></div>';
|
|
2883
|
+
if (single) {
|
|
2884
|
+
box.insertAdjacentHTML('beforeend', '<button type="button" class="mozert-lb-prev" hidden></button><button type="button" class="mozert-lb-next" hidden></button>');
|
|
2885
|
+
}
|
|
2886
|
+
document.body.appendChild(box);
|
|
2887
|
+
document.documentElement.classList.add('mozert-lb-open');
|
|
2888
|
+
show(i);
|
|
2889
|
+
requestAnimationFrame(() => box && box.classList.add('mozert-lb-show'));
|
|
2890
|
+
const stage = box.querySelector('.mozert-lb-stage');
|
|
2891
|
+
box.querySelector('.mozert-lb-close').addEventListener('click', close);
|
|
2892
|
+
const pv = box.querySelector('.mozert-lb-prev');
|
|
2893
|
+
const nx = box.querySelector('.mozert-lb-next');
|
|
2894
|
+
pv.addEventListener('click', () => show(idx - 1, -1));
|
|
2895
|
+
nx.addEventListener('click', () => show(idx + 1, 1));
|
|
2896
|
+
const zb = box.querySelector('.mozert-lb-zoom');
|
|
2897
|
+
if (zb) zb.addEventListener('click', () => setZoom(!zoomed));
|
|
2898
|
+
// Click outside the picture closes, click on the picture toggles zoom, swipe changes picture.
|
|
2899
|
+
stage.addEventListener('click', (e) => {
|
|
2900
|
+
if (drag && drag.moved) return;
|
|
2901
|
+
if (e.target.closest && e.target.closest('.mozert-lb-img,.mozert-lb-clone')) setZoom(!zoomed, e);
|
|
2902
|
+
else if (e.target === stage) close();
|
|
2903
|
+
});
|
|
2904
|
+
stage.addEventListener('pointerdown', (e) => {
|
|
2905
|
+
drag = { x: e.clientX, y: e.clientY, moved: false, id: e.pointerId };
|
|
2906
|
+
});
|
|
2907
|
+
stage.addEventListener('pointermove', (e) => {
|
|
2908
|
+
// While zoomed, the mouse pans the picture (the zoom follows the pointer).
|
|
2909
|
+
if (zoomed && zrect && e.pointerType === 'mouse' && !drag) {
|
|
2910
|
+
const z = box.querySelector('.mozert-lb-img,.mozert-lb-clone');
|
|
2911
|
+
if (z) z.style.transformOrigin = clampN(((e.clientX - zrect.left) / zrect.width) * 100, 0, 100) + '% ' + clampN(((e.clientY - zrect.top) / zrect.height) * 100, 0, 100) + '%';
|
|
2912
|
+
}
|
|
2913
|
+
if (!drag) return;
|
|
2914
|
+
const dx = e.clientX - drag.x;
|
|
2915
|
+
if (Math.abs(dx) > 6 || Math.abs(e.clientY - drag.y) > 6) drag.moved = true;
|
|
2916
|
+
});
|
|
2917
|
+
stage.addEventListener('pointerup', (e) => {
|
|
2918
|
+
if (!drag) return;
|
|
2919
|
+
const dx = e.clientX - drag.x;
|
|
2920
|
+
const moved = drag.moved;
|
|
2921
|
+
if (moved && !zoomed && Math.abs(dx) > 50) show(idx + (dx < 0 ? 1 : -1), dx < 0 ? 1 : -1);
|
|
2922
|
+
setTimeout(() => (drag = null), 0);
|
|
2923
|
+
});
|
|
2924
|
+
document.addEventListener('keydown', key, true);
|
|
2925
|
+
(box.querySelector('.mozert-lb-close') || box).focus();
|
|
2926
|
+
if (sl.emit) sl.emit('lightboxOpen', i);
|
|
2927
|
+
};
|
|
2928
|
+
const close = () => {
|
|
2929
|
+
if (!box) return;
|
|
2930
|
+
const b = box;
|
|
2931
|
+
box = null;
|
|
2932
|
+
document.removeEventListener('keydown', key, true);
|
|
2933
|
+
document.documentElement.classList.remove('mozert-lb-open');
|
|
2934
|
+
b.classList.remove('mozert-lb-show');
|
|
2935
|
+
setTimeout(() => b.parentNode && b.parentNode.removeChild(b), 180);
|
|
2936
|
+
if (sl.active && idx !== sl.current && sl.canNav) sl.goTo(idx);
|
|
2937
|
+
if (opener && opener.focus) {
|
|
2938
|
+
try {
|
|
2939
|
+
opener.focus({ preventScroll: true });
|
|
2940
|
+
} catch (e) {}
|
|
2941
|
+
}
|
|
2942
|
+
if (sl.emit) sl.emit('lightboxClose', idx);
|
|
2943
|
+
};
|
|
2944
|
+
sl.el.classList.add('mozert-has-lightbox');
|
|
2945
|
+
sl._on(sl.list, 'click', (e) => {
|
|
2946
|
+
const slide = e.target.closest && e.target.closest('.mozert-slide');
|
|
2947
|
+
if (!slide || !sl.track.contains(slide)) return;
|
|
2948
|
+
if (e.target.closest('button, input, select, textarea, [data-no-lightbox]')) return;
|
|
2949
|
+
const a = e.target.closest('a[href]');
|
|
2950
|
+
if (a && !isImg(a.getAttribute('href'))) return;
|
|
2951
|
+
const i = parseInt(slide.getAttribute('data-mozert-index'), 10);
|
|
2952
|
+
if (isNaN(i)) return;
|
|
2953
|
+
if (a) e.preventDefault();
|
|
2954
|
+
open(i % Math.max(1, items().length));
|
|
2955
|
+
});
|
|
2956
|
+
sl.lightbox = { open, close, get isOpen() { return !!box; } };
|
|
2957
|
+
return {
|
|
2958
|
+
destroy() {
|
|
2959
|
+
if (box) {
|
|
2960
|
+
box.parentNode && box.parentNode.removeChild(box);
|
|
2961
|
+
box = null;
|
|
2962
|
+
document.removeEventListener('keydown', key, true);
|
|
2963
|
+
document.documentElement.classList.remove('mozert-lb-open');
|
|
2964
|
+
}
|
|
2965
|
+
sl.el.classList.remove('mozert-has-lightbox');
|
|
2966
|
+
delete sl.lightbox;
|
|
2967
|
+
},
|
|
2968
|
+
};
|
|
2969
|
+
},
|
|
2970
|
+
});
|
|
2971
|
+
|
|
2972
|
+
/* ---------------------------------------------------------- magnifier */
|
|
2973
|
+
// magnifier: true | { zoom: 2.2, button: true, active: false }. Hover zoom like a product page: with the magnifier on,
|
|
2974
|
+
// the picture under the mouse is enlarged around the pointer. A small button in the top right corner turns it on and off.
|
|
2975
|
+
// A slide's image can name a sharper file with data-zoom (or data-full), which is loaded the first time it is magnified.
|
|
2976
|
+
Mozert.use({
|
|
2977
|
+
name: 'magnifier',
|
|
2978
|
+
defaults: { magnifier: false },
|
|
2979
|
+
create(sl) {
|
|
2980
|
+
if (!sl.s.magnifier) return null;
|
|
2981
|
+
const o = objOpt(sl.s.magnifier, { zoom: 2.2, button: true, active: false });
|
|
2982
|
+
let on = sl._magOn !== undefined ? sl._magOn : !!o.active;
|
|
2983
|
+
let cur = null;
|
|
2984
|
+
let btn = null;
|
|
2985
|
+
let pressed = false;
|
|
2986
|
+
sl.el.classList.add('mozert-has-magnifier');
|
|
2987
|
+
const targetOf = (slide) => slide.querySelector('img,picture,canvas,video') || slide.firstElementChild;
|
|
2988
|
+
const reset = () => {
|
|
2989
|
+
if (!cur) return;
|
|
2990
|
+
cur.node.style.transform = '';
|
|
2991
|
+
cur.node.style.transformOrigin = '';
|
|
2992
|
+
cur.node.classList.remove('mozert-mag-node');
|
|
2993
|
+
cur.slide.classList.remove('mozert-mag-host');
|
|
2994
|
+
cur = null;
|
|
2995
|
+
};
|
|
2996
|
+
const hires = (node) => {
|
|
2997
|
+
const img = node.tagName === 'IMG' ? node : node.querySelector && node.querySelector('img');
|
|
2998
|
+
const url = img && (img.getAttribute('data-zoom') || img.getAttribute('data-full'));
|
|
2999
|
+
if (!img || !url || img.__mozMag) return;
|
|
3000
|
+
img.__mozMag = 1;
|
|
3001
|
+
const pre = new Image();
|
|
3002
|
+
pre.onload = () => {
|
|
3003
|
+
img.src = url;
|
|
3004
|
+
};
|
|
3005
|
+
pre.src = url;
|
|
3006
|
+
};
|
|
3007
|
+
const move = (e) => {
|
|
3008
|
+
if (!on || pressed || e.pointerType !== 'mouse') return;
|
|
3009
|
+
const slide = e.target.closest && e.target.closest('.mozert-slide');
|
|
3010
|
+
if (!slide || !sl.track.contains(slide) || (e.target.closest && e.target.closest('.mozert-mag-btn'))) {
|
|
3011
|
+
reset();
|
|
3012
|
+
return;
|
|
3013
|
+
}
|
|
3014
|
+
if (!cur || cur.slide !== slide) {
|
|
3015
|
+
reset();
|
|
3016
|
+
const node = targetOf(slide);
|
|
3017
|
+
if (!node) return;
|
|
3018
|
+
cur = { slide, node };
|
|
3019
|
+
slide.classList.add('mozert-mag-host');
|
|
3020
|
+
node.classList.add('mozert-mag-node');
|
|
3021
|
+
hires(node);
|
|
3022
|
+
}
|
|
3023
|
+
const r = cur.slide.getBoundingClientRect();
|
|
3024
|
+
cur.node.style.transformOrigin = clampN(((e.clientX - r.left) / r.width) * 100, 0, 100) + '% ' + clampN(((e.clientY - r.top) / r.height) * 100, 0, 100) + '%';
|
|
3025
|
+
cur.node.style.transform = 'scale(' + o.zoom + ')';
|
|
3026
|
+
};
|
|
3027
|
+
const paint = () => {
|
|
3028
|
+
sl.el.classList.toggle('mozert-mag-on', on);
|
|
3029
|
+
if (btn) {
|
|
3030
|
+
btn.setAttribute('aria-pressed', on ? 'true' : 'false');
|
|
3031
|
+
btn.classList.toggle('mozert-on', on);
|
|
3032
|
+
}
|
|
3033
|
+
if (!on) reset();
|
|
3034
|
+
};
|
|
3035
|
+
const set = (v) => {
|
|
3036
|
+
on = !!v;
|
|
3037
|
+
sl._magOn = on;
|
|
3038
|
+
paint();
|
|
3039
|
+
sl.emit('magnifier', on);
|
|
3040
|
+
};
|
|
3041
|
+
sl._on(sl.list, 'pointermove', move);
|
|
3042
|
+
sl._on(sl.list, 'pointerleave', reset);
|
|
3043
|
+
// Dragging the slider must feel normal: drop the zoom while the button is held down.
|
|
3044
|
+
sl._on(sl.list, 'pointerdown', () => {
|
|
3045
|
+
pressed = true;
|
|
3046
|
+
reset();
|
|
3047
|
+
});
|
|
3048
|
+
sl._on(window, 'pointerup', () => {
|
|
3049
|
+
pressed = false;
|
|
3050
|
+
});
|
|
3051
|
+
sl._sub('beforeChange', reset);
|
|
3052
|
+
if (o.button) {
|
|
3053
|
+
btn = mk('button', 'mozert-mag-btn');
|
|
3054
|
+
btn.type = 'button';
|
|
3055
|
+
btn.setAttribute('aria-label', sl._label('magnifier'));
|
|
3056
|
+
btn.innerHTML = '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="10.5" cy="10.5" r="6.5"/><path d="M15.5 15.5L21 21"/></svg>';
|
|
3057
|
+
btn.addEventListener('click', (e) => {
|
|
3058
|
+
e.stopPropagation();
|
|
3059
|
+
set(!on);
|
|
3060
|
+
});
|
|
3061
|
+
sl.el.appendChild(btn);
|
|
3062
|
+
}
|
|
3063
|
+
paint();
|
|
3064
|
+
sl.magnifier = { enable: () => set(true), disable: () => set(false), toggle: () => set(!on), isOn: () => on };
|
|
3065
|
+
return {
|
|
3066
|
+
destroy() {
|
|
3067
|
+
reset();
|
|
3068
|
+
if (btn && btn.parentNode) btn.parentNode.removeChild(btn);
|
|
3069
|
+
sl.el.classList.remove('mozert-has-magnifier', 'mozert-mag-on');
|
|
3070
|
+
delete sl.magnifier;
|
|
3071
|
+
},
|
|
3072
|
+
};
|
|
3073
|
+
},
|
|
3074
|
+
});
|
|
3075
|
+
|
|
3076
|
+
/* -------------------------------------------------------- scrollDriven */
|
|
3077
|
+
// scrollDriven: true | { speed: 1.6, step: 0, top: 'center' | 0, offset: 0 }.
|
|
3078
|
+
// The slider sticks while the user scrolls and the scroll moves the slides. `speed` is how many px the slides travel
|
|
3079
|
+
// per px of page scroll (higher = less page height used); `step` forces a fixed number of px per slide instead.
|
|
3080
|
+
// `top: 'center'` pins the slider in the middle of the screen, a number pins it that many px from the top;
|
|
3081
|
+
// `offset` adds room for a sticky header. Works with non-looping, horizontal sliders.
|
|
3082
|
+
Mozert.use({
|
|
3083
|
+
name: 'scrollDriven',
|
|
3084
|
+
defaults: { scrollDriven: false },
|
|
3085
|
+
create(sl) {
|
|
3086
|
+
const c = sl.s.scrollDriven;
|
|
3087
|
+
if (!c || sl.s.vertical || sl.s.infinite || sl.s.stacked || !sl.canNav) return null;
|
|
3088
|
+
const o = objOpt(c, { step: 0, top: 0, speed: 1.6, offset: 0, fill: false, bottom: 0 });
|
|
3089
|
+
const el = sl.el;
|
|
3090
|
+
const wrap = mk('div', 'mozert-sd');
|
|
3091
|
+
el.parentNode.insertBefore(wrap, el);
|
|
3092
|
+
wrap.appendChild(el);
|
|
3093
|
+
el.classList.add('mozert-sd-stick');
|
|
3094
|
+
let topPx = 0;
|
|
3095
|
+
const place = () => {
|
|
3096
|
+
if (o.fill) {
|
|
3097
|
+
// fill: the pinned slider takes the whole screen height (minus offset/bottom), so there is no empty space around it
|
|
3098
|
+
topPx = (o.top === 'center' ? 0 : parseFloat(o.top) || 0) + (parseFloat(o.offset) || 0);
|
|
3099
|
+
el.classList.add('mozert-sd-fill');
|
|
3100
|
+
el.style.height = Math.max(160, window.innerHeight - topPx - (parseFloat(o.bottom) || 0)) + 'px';
|
|
3101
|
+
el.style.top = topPx + 'px';
|
|
3102
|
+
return;
|
|
3103
|
+
}
|
|
3104
|
+
const h = el.offsetHeight;
|
|
3105
|
+
topPx = o.top === 'center' ? Math.max(0, Math.round((window.innerHeight - h) / 2)) : parseFloat(o.top) || 0;
|
|
3106
|
+
topPx += parseFloat(o.offset) || 0;
|
|
3107
|
+
el.style.top = topPx + 'px';
|
|
3108
|
+
};
|
|
3109
|
+
let raf = 0;
|
|
3110
|
+
let lastIdx = -1;
|
|
3111
|
+
const steps = () => Math.max(1, sl.maxIndex);
|
|
3112
|
+
const totalPx = () => {
|
|
3113
|
+
if (o.step) return steps() * o.step;
|
|
3114
|
+
const travel = Math.abs(sl._pos(sl.maxIndex) - sl._pos(0));
|
|
3115
|
+
return Math.max(240, Math.round(travel / Math.max(0.2, o.speed)));
|
|
3116
|
+
};
|
|
3117
|
+
const size = () => {
|
|
3118
|
+
place();
|
|
3119
|
+
wrap.style.height = el.offsetHeight + totalPx() + 'px';
|
|
3120
|
+
};
|
|
3121
|
+
const update = () => {
|
|
3122
|
+
raf = 0;
|
|
3123
|
+
if (!sl.active) return;
|
|
3124
|
+
const r = wrap.getBoundingClientRect();
|
|
3125
|
+
const total = totalPx();
|
|
3126
|
+
const p = clampN((topPx - r.top) / total, 0, 1);
|
|
3127
|
+
const f = p * steps();
|
|
3128
|
+
const i0 = Math.floor(f);
|
|
3129
|
+
const i1 = Math.min(sl.maxIndex, i0 + 1);
|
|
3130
|
+
const k = f - i0;
|
|
3131
|
+
const idx = Math.round(f);
|
|
3132
|
+
if (idx !== lastIdx) {
|
|
3133
|
+
lastIdx = idx;
|
|
3134
|
+
sl.goTo(idx, true);
|
|
3135
|
+
}
|
|
3136
|
+
sl._setPos(sl._pos(i0) + (sl._pos(i1) - sl._pos(i0)) * k, false);
|
|
3137
|
+
wrap.style.setProperty('--sd-progress', p.toFixed(4));
|
|
3138
|
+
};
|
|
3139
|
+
const queue = () => {
|
|
3140
|
+
if (!raf) raf = requestAnimationFrame(update);
|
|
3141
|
+
};
|
|
3142
|
+
size();
|
|
3143
|
+
sl._on(window, 'scroll', queue, { passive: true });
|
|
3144
|
+
sl._on(window, 'resize', () => {
|
|
3145
|
+
size();
|
|
3146
|
+
queue();
|
|
3147
|
+
});
|
|
3148
|
+
sl._sub('reInit', () => {
|
|
3149
|
+
size();
|
|
3150
|
+
queue();
|
|
3151
|
+
});
|
|
3152
|
+
update();
|
|
3153
|
+
sl.scrollDriven = { update };
|
|
3154
|
+
return {
|
|
3155
|
+
destroy() {
|
|
3156
|
+
if (raf) cancelAnimationFrame(raf);
|
|
3157
|
+
el.classList.remove('mozert-sd-stick', 'mozert-sd-fill');
|
|
3158
|
+
el.style.top = '';
|
|
3159
|
+
if (o.fill) el.style.height = '';
|
|
3160
|
+
if (wrap.parentNode) {
|
|
3161
|
+
wrap.parentNode.insertBefore(el, wrap);
|
|
3162
|
+
wrap.parentNode.removeChild(wrap);
|
|
3163
|
+
}
|
|
3164
|
+
delete sl.scrollDriven;
|
|
3165
|
+
},
|
|
3166
|
+
};
|
|
3167
|
+
},
|
|
3168
|
+
});
|
|
2615
3169
|
})();
|
|
2616
3170
|
|
|
2617
3171
|
//#endregion core
|