From 13eb74045a8c25aeb335be9688b3889405dce4e5 Mon Sep 17 00:00:00 2001 From: Karel-Jan Van Haute Date: Wed, 12 Feb 2025 18:34:55 +0100 Subject: [PATCH 1/2] Glide.js vervangen door Swiper? Fixes #417 --- package.json | 1 + tailoff/js/components/glide.component.d.ts | 197 ------------------ tailoff/js/components/glide.component.ts | 187 ----------------- tailoff/js/components/swiper.component.ts | 21 ++ tailoff/js/site.ts | 10 +- .../_snippet/_content/_blocks/_slider.twig | 44 ---- templates/_site/_snippet/_item/_card.twig | 2 +- templates/jsPlugins/slider.twig | 121 ++++------- yarn.lock | 5 + 9 files changed, 72 insertions(+), 516 deletions(-) delete mode 100644 tailoff/js/components/glide.component.d.ts delete mode 100644 tailoff/js/components/glide.component.ts create mode 100644 tailoff/js/components/swiper.component.ts delete mode 100644 templates/_site/_snippet/_content/_blocks/_slider.twig diff --git a/package.json b/package.json index 7aec969a..f38a8e9d 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "@popperjs/core": "^2.11.8", "flatpickr": "^4.6.13", "leaflet": "^1.9.4", + "swiper": "^11.2.0", "tailwind": "^4.0.0", "tippy.js": "^6.3.7", "vite-plugin-dynamic-import": "^1.5.0" diff --git a/tailoff/js/components/glide.component.d.ts b/tailoff/js/components/glide.component.d.ts deleted file mode 100644 index d2f434c7..00000000 --- a/tailoff/js/components/glide.component.d.ts +++ /dev/null @@ -1,197 +0,0 @@ -// Type definitions for @glidejs/glide 3.2.6 -// Project: https://glidejs.com/ -// Definitions by: Ota Mares - -declare module "@glidejs/glide" { - import Glide from "@glidejs/glide/dist/glide.modular.esm"; - export default Glide; -} - -declare module "@glidejs/glide/dist/glide.modular.esm" { - export type GlideType = "slider" | "carousel"; - export type GlideEvent = - | "mount.before" - | "mount.after" - | "update" - | "play" - | "pause" - | "build.before" - | "build.after" - | "run.before" - | "run" - | "run.after" - | "run.offset" - | "run.start" - | "run.end" - | "move" - | "move.after" - | "resize" - | "swipe.start" - | "swipe.move" - | "swipe.end" - | "translate.jump"; - - declare interface GlideOptions { - type?: GlideType; - startAt?: number; - perView?: number; - focusAt?: number; - gap?: number; - autoplay?: boolean; - hoverpause?: boolean; - keyboard?: boolean; - bound?: boolean; - swipeThreshold?: number; - dragThreshold?: number; - perTouch?: boolean; - touchRatio?: number; - touchAngle?: number; - animationDuration?: number; - rewind?: boolean; - rewindDuration?: number; - animationTimingFunc?: string; - throttle?: number; - direction?: string; - peek?: number; - breakpoints?: object; - classes?: object; - } - - export declare class DefaultOptions implements GlideOptions { - public type?: GlideType = "slider"; - public startAt?: number = 0; - public perView?: number = 1; - public focusAt?: number = 0; - public gap?: number = 10; - public autoplay?: boolean = false; - public hoverpause?: boolean = true; - public keyboard?: boolean = true; - public bound?: boolean = false; - public swipeThreshold?: number = 80; - public dragThreshold?: number = 120; - public perTouch?: boolean = false; - public touchRatio?: number = 0.5; - public touchAngle?: number = 45; - public animationDuration?: number = 400; - public rewind?: boolean = true; - public rewindDuration?: number = 800; - public animationTimingFunc?: string = "cubic-bezier(.165, .840, .440, 1)"; - public throttle?: number = 10; - public direction?: string = "ltr"; - public peek?: number = 0; - public breakpoints?: object = {}; - public classes?: { - direction?: { - ltr?: string; - rtl?: string; - }; - slider?: string; - carousel?: string; - swipeable?: string; - dragging?: string; - cloneSlide?: string; - activeNav?: string; - activeSlide?: string; - disabledArrow?: string; - }; - } - - export declare interface EventsBus { - on( - event: GlideEvent | GlideEvent[], - handler: (context?: any) => void - ): void; - emit(event: GlideEvent | GlideEvent[], context: object): void; - } - - export default class Glide { - public index: number; - public settings: GlideOptions; - public type: GlideType; - public disabled: boolean; - - constructor(selector: string, options?: GlideOptions); - public mount(extensions: object): Glide; - public mutate( - transformers: (( - glide: Glide, - components: object, - events: EventsBus - ) => void)[] - ): Glide; - public update(settings: GlideOptions): Glide; - public destroy(): Glide; - public on( - event: GlideEvent | GlideEvent[], - callback: (context?: any) => void - ): Glide; - public go(pattern: string | boolean): Glide; - public pause(): Glide; - public play(force: number): Glide; - public disable(): Glide; - public enable(): Glide; - public isType(name: GlideType): boolean; - } - - declare interface ControlsInterface { - items: HTMLElement[]; - mount(): void; - setActive(): void; - removeActive(): void; - addClass(controls: HTMLElement): void; - removeClass(controls: HTMLElement): void; - addBindings(): void; - removeBindings(): void; - bind(elements: HTMLCollection): void; - unbind(elements: HTMLCollection): void; - click(event: Event): void; - } - - export const Controls: ControlsInterface; - - declare interface AnchorsInterface { - items: HTMLElement[]; - mount(): void; - bind(): void; - unbind(): void; - detach(): AnchorsInterface; - attach(): AnchorsInterface; - } - - export const Anchors: AnchorsInterface; - - declare interface AutoplayInterface { - time: number; - mount(): void; - bind(): void; - unbind(): void; - start(): void; - stop(): void; - } - - export const Autoplay: AutoplayInterface; - - declare interface BreakpointsInterface { - match(breakpoints: object): object; - } - - export const Breakpoints: BreakpointsInterface; - - declare interface ImagesInterface { - mount(): void; - bind(): void; - unbind(): void; - dragstart(event: Event): void; - } - - export const Images: ImagesInterface; - - declare interface KeyboardInterface { - mount(): void; - bind(): void; - unbind(): void; - press(event: Event): void; - } - - export const Keyboard: KeyboardInterface; -} diff --git a/tailoff/js/components/glide.component.ts b/tailoff/js/components/glide.component.ts deleted file mode 100644 index 6cf13a6a..00000000 --- a/tailoff/js/components/glide.component.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { DOMHelper } from '../utils/domHelper'; -import { Info } from '../utils/info'; - -export class GlideComponent { - constructor() { - const sliders = Array.from(document.querySelectorAll('.js-slider')); - if (sliders.length > 0) { - this.processSliders(sliders); - } - } - - private async processSliders(sliders: Array) { - // @ts-ignore - const Glide = await import('@glidejs/glide'); - sliders.forEach((slider) => { - slider.classList.remove('js-slider'); - const sliderID = slider.getAttribute('id'); - - if (sliderID.indexOf('carousel') >= 0) { - const glide = new Glide.default('#' + sliderID, { - type: 'carousel', - gap: 0, - perView: 1, - }); - - glide.on(['mount.after', 'resize'], function (e) { - if (window.innerWidth < 480) { - glide.update({ peek: 40 }); - } else { - glide.update({ peek: 0 }); - } - }); - - glide.mount(); - } - - if (sliderID.indexOf('slider') >= 0) { - const idealWidth = parseInt(slider.getAttribute('data-ideal-width')) || 200; - const peek = slider.hasAttribute('data-peek') ? parseInt(slider.getAttribute('data-peek')) : 100; - const gap = slider.hasAttribute('data-gap') ? parseInt(slider.getAttribute('data-gap')) : 20; - const animationDuration = slider.hasAttribute('data-animation-duration') - ? parseInt(slider.getAttribute('data-animation-duration')) - : 800; - - const glide = new Glide.default('#' + sliderID, { - type: 'slider', - perView: 1, - animationDuration: animationDuration, - gap: gap, - peek: peek, - perTouch: 1, - }); - - glide.on(['mount.after', 'resize'], function (e) { - const slider = document.getElementById(sliderID); - const slides = slider.querySelectorAll('.glide__slide'); - if (window.innerWidth < 480) { - glide.update({ peek: 40 }); - } else { - glide.update({ peek: peek }); - } - const availableWidth = slider.offsetWidth - 2 * glide.settings.peek; - let possiblePerView = Math.floor(availableWidth / idealWidth); - - if (possiblePerView <= 0) { - possiblePerView = 1; - } - glide.update({ perView: possiblePerView }); - - const controles = slider.querySelector("div[data-glide-el='controls']"); - - if ((controles && Info.isTouchDevice() && Info.isMobile()) || possiblePerView >= slides.length) { - controles.classList.add('hidden'); - } else { - controles.classList.remove('hidden'); - } - if (possiblePerView >= slides.length) { - if (glide.index > 0) { - glide.go('<<'); - } - glide.disable(); - } else { - glide.enable(); - } - slides.forEach((slide: HTMLElement) => { - slide.style.transitionProperty = 'opacity'; - slide.style.transitionDuration = glide.settings.animationDuration + 'ms'; - slide.style.transitionTimingFunction = 'ease'; - }); - - let start = glide.index; - let end = start + glide.settings.perView; - Array.from(slides).forEach((slide, i) => { - if (i < start || i >= end) { - if (i + 1 == start || i == end) { - slide.classList.add('opacity-50'); - } else if (i + 2 == start || i - 1 == end) { - slide.classList.add('opacity-25'); - } else { - slide.classList.add('opacity-0'); - } - slide.classList.add('pointer-events-none'); - } - }); - }); - - glide.on(['run.before', 'resize'], (event) => { - const slider = document.getElementById(sliderID); - const slides = slider.querySelectorAll('.glide__slide'); - - let amount = glide.settings.perView; - - if (slides.length - (glide.index + glide.settings.perView) < amount) { - amount = slides.length - glide.settings.perView; - } - - event.steps = event.direction === '>' ? -amount : amount; - let start = glide.index; - let end = start + glide.settings.perView; - Array.from(slides).forEach((slide, i) => { - if ((event.direction === '>' && i == start) || (event.direction === '<' && i + 1 == end)) { - slide.classList.add('opacity-50'); - slide.classList.add('pointer-events-none'); - } else if ((event.direction === '>' && i + 1 == start) || (event.direction === '<' && i == end)) { - slide.classList.remove('opacity-50'); - slide.classList.add('opacity-25'); - } else if ((event.direction === '>' && i + 2 == start) || (event.direction === '<' && i - 1 == end)) { - slide.classList.remove('opacity-25'); - slide.classList.add('opacity-0'); - } - if ((event.direction === '>' && i == end) || (event.direction === '<' && i + 1 == start)) { - slide.classList.remove('opacity-50'); - slide.classList.remove('pointer-events-none'); - } else if ((event.direction === '>' && i - 1 == end) || (event.direction === '<' && i + 2 == start)) { - slide.classList.add('opacity-50'); - slide.classList.remove('opacity-25'); - } else if ((event.direction === '>' && i - 2 == end) || (event.direction === '<' && i + 3 == start)) { - slide.classList.add('opacity-25'); - slide.classList.remove('opacity-0'); - } - }); - }); - - glide.on(['mount.after', 'run.after', 'resize'], (event) => { - const slider = document.getElementById(sliderID); - const slides = slider.querySelectorAll('.glide__slide'); - - const prevController = slider.querySelector( - "div[data-glide-el='controls'] .glide__arrow--left" - ) as HTMLButtonElement; - const nextController = slider.querySelector( - "div[data-glide-el='controls'] .glide__arrow--right" - ) as HTMLButtonElement; - if (glide.index == 0) { - prevController.classList.add('opacity-25'); - prevController.classList.add('pointer-events-none'); - prevController.classList.remove('pointer-events-auto'); - prevController.disabled = true; - } else { - prevController.classList.remove('opacity-25'); - prevController.classList.remove('pointer-events-none'); - prevController.classList.add('pointer-events-auto'); - prevController.disabled = false; - } - - if (glide.index + glide.settings.perView >= slides.length) { - nextController.classList.add('opacity-25'); - nextController.classList.add('pointer-events-none'); - nextController.classList.remove('pointer-events-auto'); - nextController.disabled = true; - } else { - nextController.classList.remove('opacity-25'); - nextController.classList.remove('pointer-events-none'); - nextController.classList.add('pointer-events-auto'); - nextController.disabled = false; - } - }); - - window.addEventListener('load', function () { - glide.mount(); - }); - - glide.mount(); - } - }); - } -} diff --git a/tailoff/js/components/swiper.component.ts b/tailoff/js/components/swiper.component.ts new file mode 100644 index 00000000..2eea69ab --- /dev/null +++ b/tailoff/js/components/swiper.component.ts @@ -0,0 +1,21 @@ +import Swiper from 'swiper'; +import { Navigation, A11y } from 'swiper/modules'; + +import 'swiper/css'; +// import 'swiper/css/navigation'; + +export class SwiperComponent { + constructor() { + const swiper = new Swiper('.swiper', { + modules: [Navigation, A11y], + slidesPerView: 'auto', + // slideToClickedSlide: true, + watchSlidesProgress: true, + navigation: { + nextEl: '.swiper-button-next', + prevEl: '.swiper-button-prev', + lockClass: 'hidden', + }, + }); + } +} diff --git a/tailoff/js/site.ts b/tailoff/js/site.ts index efe81028..37515134 100644 --- a/tailoff/js/site.ts +++ b/tailoff/js/site.ts @@ -86,11 +86,6 @@ const components = [ className: 'formOtherRadioComponent', selector: '.js-other-radio', }, - { - name: 'glide', - className: 'GlideComponent', - selector: '.js-slider', - }, { name: 'googleMaps', className: 'GoogleMapsComponent', @@ -185,6 +180,11 @@ const components = [ className: 'StickyHeader', selector: '[data-s-sticky-header]', }, + { + name: 'swiper', + className: 'SwiperComponent', + selector: '.swiper', + }, { name: 'table', className: 'TableComponent', diff --git a/templates/_site/_snippet/_content/_blocks/_slider.twig b/templates/_site/_snippet/_content/_blocks/_slider.twig deleted file mode 100644 index bcd8622e..00000000 --- a/templates/_site/_snippet/_content/_blocks/_slider.twig +++ /dev/null @@ -1,44 +0,0 @@ -{% if block.images.collect()|length %} -
-
-
- -
-
- - -
-
-
-{% endif %} diff --git a/templates/_site/_snippet/_item/_card.twig b/templates/_site/_snippet/_item/_card.twig index a08c1976..0ab4cc5b 100644 --- a/templates/_site/_snippet/_item/_card.twig +++ b/templates/_site/_snippet/_item/_card.twig @@ -17,7 +17,7 @@
{% if showImage %} - {% set overviewImage = cardEntry.overviewImage.eagerly().one() ? cardEntry.overviewImage.eagerly().one() : fallback.image.one() %} + {% set overviewImage = cardEntry.overviewImage|length ? cardEntry.overviewImage.eagerly().one() : fallback.image.one() %} {% if overviewImage|length %} {% set optimizedImage = overviewImage.optimizedOverview %} {% if optimizedImage|length %} diff --git a/templates/jsPlugins/slider.twig b/templates/jsPlugins/slider.twig index 87a450b4..60a865dd 100644 --- a/templates/jsPlugins/slider.twig +++ b/templates/jsPlugins/slider.twig @@ -18,101 +18,58 @@ {% include '_site/_snippet/_nav/_breadcrumb' %}

Slider Example

-

This is an example on how to use the glide plugin.

+

This is an example on how to use the swiper plugin.

-
+
-
-
-
-
+
-
-
-
-
- {% set news = craft.entries.section('news').with(['overviewImage']).orderBy('postDate DESC') %} - {% for cardEntry in news %} -
- {% include '_site/_snippet/_item/_card' %} -
- {% endfor %} +
+
+ + +
+
+ {% set news = craft.entries.section('news').with(['overviewImage']).orderBy('postDate DESC') %} + {% for cardEntry in news %} +
+ {% include '_site/_snippet/_item/_card' %}
-
-
- - -
+ {% endfor %}
diff --git a/yarn.lock b/yarn.lock index 9058f72a..e29ac6e7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6548,6 +6548,11 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +swiper@^11.2.0: + version "11.2.3" + resolved "https://registry.yarnpkg.com/swiper/-/swiper-11.2.3.tgz#6358e8169dcd78fc1dffb65bb29b5875c7bcedcc" + integrity sha512-24m5tqHCd1Wmp9+86aKDoIGMsZGEjIL++3nuB1UjhAhIlvwj4k0Jikxu9PGQ/VswLpoje6JtMDWo12uu4aS2FA== + symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" From 398baedb97268b5543fa3c5a51803ff1f1ecae27 Mon Sep 17 00:00:00 2001 From: Emily Berghen Date: Mon, 14 Apr 2025 11:34:21 +0200 Subject: [PATCH 2/2] Fix Swiper component load --- tailoff/js/components/swiper.component.ts | 2 +- tailoff/js/site.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tailoff/js/components/swiper.component.ts b/tailoff/js/components/swiper.component.ts index 2eea69ab..9ccb302f 100644 --- a/tailoff/js/components/swiper.component.ts +++ b/tailoff/js/components/swiper.component.ts @@ -4,7 +4,7 @@ import { Navigation, A11y } from 'swiper/modules'; import 'swiper/css'; // import 'swiper/css/navigation'; -export class SwiperComponent { +export default class SwiperComponent { constructor() { const swiper = new Swiper('.swiper', { modules: [Navigation, A11y], diff --git a/tailoff/js/site.ts b/tailoff/js/site.ts index 1be7dd53..8b9e2375 100644 --- a/tailoff/js/site.ts +++ b/tailoff/js/site.ts @@ -153,7 +153,6 @@ const components = [ }, { name: 'swiper', - className: 'SwiperComponent', selector: '.swiper', }, {