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