Skip to content
Snippets Groups Projects
Verified Commit dac45474 authored by Volker Schukai's avatar Volker Schukai :alien:
Browse files

fix: slider with more slides #239

parent a0609408
No related branches found
No related tags found
No related merge requests found
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Slider view more slides #239</title>
<script src="239.mjs" type="module"></script>
</head>
<body>
<h1>Slider view more slides #239</h1>
<ul>
<li><a href="https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/239">Issue #239</a></li>
<li><a href="/">Back to overview</a></li>
</ul>
<main style="width: 1200px">
<div class="container">
<monster-slider
data-monster-option-slides-0="3"
>
<div slot="prev">
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="currentColor"
class="bi bi-arrow-left-square-fill" viewBox="0 0 16 16">
<path d="M16 14a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2zm-4.5-6.5H5.707l2.147-2.146a.5.5 0 1 0-.708-.708l-3 3a.5.5 0 0 0 0 .708l3 3a.5.5 0 0 0 .708-.708L5.707 8.5H11.5a.5.5 0 0 0 0-1"/>
</svg>
</div>
<div class="slide" style="height: 450px;background-color: #ff6666;">
<h1>SLIDE 1</h1>
</div>
<div class="slide" style="height: 450px;background-color: #66ff66;width:50px">
<h1>SLIDE 2</h1>
</div>
<div class="slide" style="height: 450px;background-color: #6666ff;width:50px">
<h1>SLIDE 3</h1>
</div>
<div class="slide" style="height: 450px;background-color: #ffff66;width:50px">
<h1>SLIDE 4</h1>
</div>
<div class="slide" style="height: 450px;background-color: #66ffff;width:50px">
<h1>SLIDE 5</h1>
</div>
<div class="slide" style="height: 450px;background-color: #ff66ff;width:50px">
<h1>SLIDE 6</h1>
</div>
<div class="slide" style="height: 450px;background-color: #ff66cc;width:50px">
<h1>SLIDE 7</h1>
</div>
<div slot="next">
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="currentColor"
class="bi bi-arrow-right-square-fill" viewBox="0 0 16 16">
<path d="M0 14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2a2 2 0 0 0-2 2zm4.5-6.5h5.793L8.146 5.354a.5.5 0 1 1 .708-.708l3 3a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708-.708L10.293 8.5H4.5a.5.5 0 0 1 0-1"/>
</svg>
</div>
</monster-slider>
</div>
</main>
</body>
</html>
/**
* @file development/issues/open/237.mjs
* @url https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/237
* @description new slieder control
* @issue 239
*/
import "../../../source/components/style/property.pcss";
import "../../../source/components/style/link.pcss";
import "../../../source/components/style/color.pcss";
import "../../../source/components/style/theme.pcss";
import "../../../source/components/style/normalize.pcss";
import "../../../source/components/style/typography.pcss";
import "../../../source/components/layout/slider.mjs";
import "../../../source/components/datatable/pagination.mjs";
...@@ -21,6 +21,8 @@ import { SliderStyleSheet } from "./stylesheet/slider.mjs"; ...@@ -21,6 +21,8 @@ import { SliderStyleSheet } from "./stylesheet/slider.mjs";
import {fireCustomEvent} from "../../dom/events.mjs"; import {fireCustomEvent} from "../../dom/events.mjs";
import {getWindow} from "../../dom/util.mjs"; import {getWindow} from "../../dom/util.mjs";
import {isObject, isInteger} from "../../types/is.mjs";
export {Slider}; export {Slider};
...@@ -54,18 +56,14 @@ const nextElementSymbol = Symbol("nextElement"); ...@@ -54,18 +56,14 @@ const nextElementSymbol = Symbol("nextElement");
*/ */
const configSymbol = Symbol("config"); const configSymbol = Symbol("config");
/**
* @private
* @type {string}
*/
const ATTRIBUTE_CLONE_FROM = ATTRIBUTE_PREFIX + "clone-from";
/** /**
* A Slider * A Slider
* *
* @fragments /fragments/components/layout/slider/ * @fragments /fragments/components/layout/slider/
* *
* @example /examples/components/layout/slider-simple * @example /examples/components/layout/slider-simple
* @example /examples/components/layout/slider-carousel
* @example /examples/components/layout/slider-multiple
* *
* @since 3.74.0 * @since 3.74.0
* @copyright schukai GmbH * @copyright schukai GmbH
...@@ -96,6 +94,11 @@ class Slider extends CustomElement { ...@@ -96,6 +94,11 @@ class Slider extends CustomElement {
autoPlayInterval: null, autoPlayInterval: null,
}; };
// set --monster-slides-width
const slides = this.shadowRoot.querySelector(`[${ATTRIBUTE_ROLE}="slider"]`);
const slidesVisible = getVisibleSlidesFromContainerWidth.call(this);
slides.style.setProperty("--monster-slides-width", `${100 / slidesVisible}%`);
initControlReferences.call(this); initControlReferences.call(this);
initEventHandler.call(this); initEventHandler.call(this);
initStructure.call(this); initStructure.call(this);
...@@ -111,20 +114,23 @@ class Slider extends CustomElement { ...@@ -111,20 +114,23 @@ class Slider extends CustomElement {
* *
* @property {Object} templates Template definitions * @property {Object} templates Template definitions
* @property {string} templates.main Main template * @property {string} templates.main Main template
* @property {string} actions.click="throw Error" Callback when clicked * @property {string} actions.click Callback when clicked
* @property {Object} features Features * @property {Object} features Features
* @property {boolean} features.carousel=true Carousel feature * @property {boolean} features.carousel Carousel feature
* @property {boolean} features.autoPlay=true Auto play feature * @property {boolean} features.autoPlay Auto play feature
* @property {boolean} features.thumbnails=true Thumbnails feature * @property {boolean} features.thumbnails Thumbnails feature
* @property {boolean} features.drag=true Drag feature * @property {boolean} features.drag Drag feature
* @property {Object} slides Slides configuration, an object with breakpoints and the number of slides to show
* @property {Object} carousel Carousel configuration
* @property {number} carousel.transition Transition time between a full rotation of the carousel
* @property {Object} autoPlay Auto play configuration * @property {Object} autoPlay Auto play configuration
* @property {number} autoPlay.delay=1500 Delay between slides * @property {number} autoPlay.delay Delay between slides
* @property {number} autoPlay.startDelay=1000 Start delay * @property {number} autoPlay.startDelay Start delay
* @property {string} autoPlay.direction="next" Direction of the autoplay * @property {string} autoPlay.direction Direction of the autoplay
* @property {boolean} autoPlay.mouseOverPause=true Pause on mouse over * @property {boolean} autoPlay.mouseOverPause Pause on mouse over
* @property {boolean} autoPlay.touchPause=true Pause on touch * @property {boolean} autoPlay.touchPause Pause on touch
* @property {Object} classes CSS classes * @property {Object} classes CSS classes
* @property {boolean} disabled=false Disabled state * @property {boolean} disabled Disabled state
*/ */
get defaults() { get defaults() {
return Object.assign({}, super.defaults, { return Object.assign({}, super.defaults, {
...@@ -142,6 +148,14 @@ class Slider extends CustomElement { ...@@ -142,6 +148,14 @@ class Slider extends CustomElement {
drag: true, drag: true,
}, },
slides: {
"0": 1,
},
carousel: {
transition: 250,
},
autoPlay: { autoPlay: {
delay: 1500, delay: 1500,
startDelay: 1000, startDelay: 1000,
...@@ -229,6 +243,7 @@ function initNavigation(name) { ...@@ -229,6 +243,7 @@ function initNavigation(name) {
* @private * @private
*/ */
function initStructure() { function initStructure() {
initNavigation.call(this, "next"); initNavigation.call(this, "next");
initNavigation.call(this, "prev"); initNavigation.call(this, "prev");
...@@ -236,9 +251,7 @@ function initStructure() { ...@@ -236,9 +251,7 @@ function initStructure() {
initThumbnails.call(this); initThumbnails.call(this);
} }
if (this.getOption("features.carousel")) { initShadows.call(this);
initCarousel.call(this);
}
if (this.getOption("features.autoPlay")) { if (this.getOption("features.autoPlay")) {
initAutoPlay.call(this); initAutoPlay.call(this);
...@@ -253,17 +266,14 @@ function initThumbnails() { ...@@ -253,17 +266,14 @@ function initThumbnails() {
const thumbnails = this.shadowRoot.querySelector( const thumbnails = this.shadowRoot.querySelector(
"[data-monster-role='thumbnails']", "[data-monster-role='thumbnails']",
); );
const slides = Array.from(getSlottedElements.call(this, ":scope", null));
slides.forEach((slide, index) => { const {originSlides} = getSlidesAndTotal.call(this);
originSlides.forEach((x, index) => {
const thumbnail = document.createElement("div"); const thumbnail = document.createElement("div");
thumbnail.classList.add("thumbnail"); thumbnail.classList.add("thumbnail");
thumbnail.addEventListener("click", () => { thumbnail.addEventListener("click", () => {
if (self.getOption("features.carousel")) {
this.moveTo(index + 1);
} else {
this.moveTo(index); this.moveTo(index);
}
}); });
thumbnails.appendChild(thumbnail); thumbnails.appendChild(thumbnail);
...@@ -301,18 +311,19 @@ function initAutoPlay() { ...@@ -301,18 +311,19 @@ function initAutoPlay() {
} }
self[configSymbol].autoPlayInterval = setInterval(() => { self[configSymbol].autoPlayInterval = setInterval(() => {
const {totalOriginSlides} = getSlidesAndTotal.call(self);
if (direction === "next") { if (direction === "next") {
if (self.next() === -1) { if (!self.getOption("features.carousel")&& self[configSymbol].currentIndex >= totalOriginSlides - 1) {
if (self.getOption("features.carousel")) { self[configSymbol].currentIndex = -1;
clearInterval(self[configSymbol].autoPlayInterval);
}
} }
self.next();
} else { } else {
if (self.previous() === -1) { if (!self.getOption("features.carousel") && self[configSymbol].currentIndex <= 0) {
if (self.getOption("features.carousel")) { self[configSymbol].currentIndex = totalOriginSlides;
clearInterval(self[configSymbol].autoPlayInterval);
}
} }
self.previous();
} }
}, delay); }, delay);
} }
...@@ -345,22 +356,62 @@ function initAutoPlay() { ...@@ -345,22 +356,62 @@ function initAutoPlay() {
} }
} }
function getVisibleSlidesFromContainerWidth() {
const containerWidth = this.shadowRoot.querySelector(`[${ATTRIBUTE_ROLE}="slider"]`).offsetWidth;
const slides = this.getOption("slides");
let visibleSlides = 1;
if (!isObject(slides)) {
return visibleSlides;
}
for (const key in slides) {
if (containerWidth >= key) {
visibleSlides = slides[key];
}
}
const {originSlides} = getSlidesAndTotal.call(this);
if (visibleSlides > originSlides.length) {
visibleSlides = originSlides.length - 1;
}
return visibleSlides;
}
/** /**
* @private * @private
*/ */
function initCarousel() { function initShadows() {
const {slides, totalSlides} = getSlidesAndTotal.call(this); const {slides, totalSlides} = getSlidesAndTotal.call(this);
if (this.getOption("features.carousel") && totalSlides > 2) { const slidesVisible = getVisibleSlidesFromContainerWidth.call(this);
const firstElement = slides[0].cloneNode(true);
firstElement.setAttribute(ATTRIBUTE_CLONE_FROM, 1); if (totalSlides > slidesVisible) {
let current = slides[0];
let last = slides[totalSlides - 1];
for (let i = 0; i < slidesVisible; i++) {
const clone = current.cloneNode(true);
clone.setAttribute('data-monster-clone-from', i);
last.insertAdjacentElement('afterend', clone);
current = current.nextElementSibling;
last = clone;
}
const lastElement = slides[totalSlides - 1].cloneNode(true); current = slides[totalSlides - 1];
lastElement.setAttribute(ATTRIBUTE_CLONE_FROM, totalSlides); let first = slides[0];
slides[totalSlides - 1].insertAdjacentElement("afterend", firstElement); for (let i = 0; i < slidesVisible; i++) {
const clone = current.cloneNode(true);
clone.setAttribute('data-monster-clone-from', totalSlides - i);
first.insertAdjacentElement('beforebegin', clone);
current = current.previousElementSibling;
first = clone;
}
slides[0].insertAdjacentElement("beforebegin", lastElement); moveTo.call(this, 0);
moveTo.call(this, 1);
} }
} }
...@@ -369,9 +420,13 @@ function initCarousel() { ...@@ -369,9 +420,13 @@ function initCarousel() {
* @return {{slides: unknown[], totalSlides: number}} * @return {{slides: unknown[], totalSlides: number}}
*/ */
function getSlidesAndTotal() { function getSlidesAndTotal() {
const originSlides = Array.from(getSlottedElements.call(this, ":scope:not([data-monster-clone-from])", null));
const totalOriginSlides = originSlides.length;
const slides = Array.from(getSlottedElements.call(this, ":scope", null)); const slides = Array.from(getSlottedElements.call(this, ":scope", null));
const totalSlides = slides.length; const totalSlides = slides.length;
return { slides, totalSlides };
return {originSlides, totalOriginSlides, slides, totalSlides};
} }
/** /**
...@@ -379,13 +434,8 @@ function getSlidesAndTotal() { ...@@ -379,13 +434,8 @@ function getSlidesAndTotal() {
* @return {number} * @return {number}
*/ */
function next() { function next() {
const { slides, totalSlides } = getSlidesAndTotal.call(this);
const nextIndex = this[configSymbol].currentIndex + 1; const nextIndex = this[configSymbol].currentIndex + 1;
if (nextIndex >= totalSlides) {
return -1;
}
queueMicrotask(() => { queueMicrotask(() => {
getWindow().requestAnimationFrame(() => { getWindow().requestAnimationFrame(() => {
getWindow().requestAnimationFrame(() => { getWindow().requestAnimationFrame(() => {
...@@ -404,11 +454,14 @@ function next() { ...@@ -404,11 +454,14 @@ function next() {
function prev() { function prev() {
const prevIndex = this[configSymbol].currentIndex - 1; const prevIndex = this[configSymbol].currentIndex - 1;
if (prevIndex < 0) { queueMicrotask(() => {
return -1; getWindow().requestAnimationFrame(() => {
} getWindow().requestAnimationFrame(() => {
moveTo.call(this, prevIndex); moveTo.call(this, prevIndex);
});
});
});
return 0; return 0;
} }
...@@ -418,84 +471,99 @@ function prev() { ...@@ -418,84 +471,99 @@ function prev() {
* @param index * @param index
*/ */
function setMoveProperties(slides, index) { function setMoveProperties(slides, index) {
this[configSymbol].currentIndex = index;
slides.forEach((slide) => { slides.forEach((slide) => {
slide.classList.remove("current"); slide.classList.remove("current");
}); });
let offset = -(index * 100); let offset = -(index * 100);
const slidesVisible = getVisibleSlidesFromContainerWidth.call(this);
offset = offset / slidesVisible;
if (offset !== 0) { if (offset !== 0) {
offset += "%"; offset += "%";
} }
this[sliderElementSymbol].style.transform = this[sliderElementSymbol].style.transform =
`translateX(calc(${offset} + ${this[configSymbol].draggingPos}px))`; `translateX(calc(${offset} + ${this[configSymbol].draggingPos}px))`;
slides[index].classList.add("current"); slides[index].classList.add("current");
this[configSymbol].lastOffset = offset;
} }
/** /**
* @private * @private
* @param index * @param {number} index
* @param {boolean} animation
* @fires monster-slider-moved * @fires monster-slider-moved
*/ */
function moveTo(index) { function moveTo(index, animation) {
const { slides, totalSlides } = getSlidesAndTotal.call(this);
if (index < 0) { const {slides, totalSlides, originSlides, totalOriginSlides} = getSlidesAndTotal.call(this);
index = totalSlides - 1;
} else if (index >= totalSlides) { if (animation === false) {
index = 0; this[sliderElementSymbol].classList.remove("animate");
} else {
this[sliderElementSymbol].classList.add("animate");
} }
const slider = this[sliderElementSymbol]; if (this.getOption("features.carousel") === true) {
setMoveProperties.call(this, slides, index); if (index < 0) {
index = -1;
}
const style = getComputedStyle(this[sliderElementSymbol]); if (index > totalOriginSlides) {
const duration = style.transitionDuration; index = totalOriginSlides;
const durationMilis = parseFloat(duration) * 1000; }
let slideIndex = index; } else {
let eventFired = false;
if (this.getOption("features.carousel")) { if (index < 0) {
slideIndex = index - 1; index = 0;
}
if (slides[index].hasAttribute(ATTRIBUTE_CLONE_FROM)) { if (index >= totalOriginSlides) {
const from = parseInt(slides[index].getAttribute(ATTRIBUTE_CLONE_FROM)); index = totalOriginSlides - 1;
}
}
getWindow().requestAnimationFrame(() => { if (!isInteger(index)) {
getWindow().requestAnimationFrame(() => { return;
setTimeout(() => { }
slider.style.transitionProperty = "none";
setMoveProperties.call(this, slides, from); this[configSymbol].currentIndex = index;
slideIndex = from - 1; let slidesIndex = index + getVisibleSlidesFromContainerWidth.call(this);
getWindow().requestAnimationFrame(() => { if (slidesIndex < 0) {
getWindow().requestAnimationFrame(() => { slidesIndex = totalSlides - 1 - getVisibleSlidesFromContainerWidth.call(this);
slider.style.transitionProperty = ""; this[configSymbol].currentIndex = totalOriginSlides - 1;
} else if (index > totalOriginSlides) {
slidesIndex = 0;
this[configSymbol].currentIndex = 0;
}
fireCustomEvent(this, "monster-slider-moved", { setMoveProperties.call(this, slides, slidesIndex);
index: slideIndex,
});
eventFired = true; if (index === totalOriginSlides) {
}); setTimeout(() => {
}); getWindow().requestAnimationFrame(() => {
}, durationMilis); moveTo.call(this, 0, false);
}); });
}, this.getOption("carousel.transition"));
} else if (index === -1) {
setTimeout(() => {
getWindow().requestAnimationFrame(() => {
moveTo.call(this, totalOriginSlides - 1, false);
}); });
} }, this.getOption("carousel.transition"));
} }
if (!eventFired) {
fireCustomEvent(this, "monster-slider-moved", { fireCustomEvent(this, "monster-slider-moved", {
index: slideIndex, index: index,
}); });
} }
}
/** /**
* @private * @private
...@@ -539,6 +607,11 @@ function initEventHandler() { ...@@ -539,6 +607,11 @@ function initEventHandler() {
* @param type * @param type
*/ */
function startDragging(e, type) { function startDragging(e, type) {
const {slides} = getSlidesAndTotal.call(this);
const widthOfSlider = slides[this[configSymbol].currentIndex]?.offsetWidth
this[configSymbol].isDragging = true; this[configSymbol].isDragging = true;
this[configSymbol].startPos = getPositionX(e, type); this[configSymbol].startPos = getPositionX(e, type);
this[sliderElementSymbol].classList.add("grabbing"); this[sliderElementSymbol].classList.add("grabbing");
...@@ -561,18 +634,13 @@ function startDragging(e, type) { ...@@ -561,18 +634,13 @@ function startDragging(e, type) {
this[sliderElementSymbol].style.transitionProperty = ""; this[sliderElementSymbol].style.transitionProperty = "";
const lastPos = this[configSymbol].draggingPos; const lastPos = this[configSymbol].draggingPos;
const widthOfSlider = this[sliderElementSymbol].offsetWidth;
this[configSymbol].draggingPos = 0; this[configSymbol].draggingPos = 0;
let newIndex = this[configSymbol].currentIndex; let newIndex = this[configSymbol].currentIndex;
const shift = lastPos / widthOfSlider;
const shiftIndex = Math.round(shift);
const x = lastPos / widthOfSlider; newIndex += (shiftIndex * -1);
if (x > 0.5) {
newIndex--;
} else if (x < -0.5) {
newIndex++;
}
this.moveTo(newIndex); this.moveTo(newIndex);
}; };
...@@ -599,8 +667,10 @@ function dragging(e, type) { ...@@ -599,8 +667,10 @@ function dragging(e, type) {
if (!this[configSymbol].isDragging) return; if (!this[configSymbol].isDragging) return;
this[configSymbol].draggingPos = this[configSymbol].draggingPos =
getPositionX(e, type) - this[configSymbol].startPos; getPositionX(e, type) - this[configSymbol].startPos;
const { slides, totalSlides } = getSlidesAndTotal.call(this);
setMoveProperties.call(this, slides, this[configSymbol].currentIndex); this[sliderElementSymbol].style.transform =
`translateX(calc(${this[configSymbol].lastOffset} + ${this[configSymbol].draggingPos}px))`;
} }
/** /**
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
height: 100%; height: 100%;
position: relative; position: relative;
box-sizing: border-box; box-sizing: border-box;
--monster-slides-width: 100%;
} }
[data-monster-role=slider] { [data-monster-role=slider] {
...@@ -25,10 +26,13 @@ ...@@ -25,10 +26,13 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
scrollbar-width: none; scrollbar-width: none;
transition: all 0.3s ease-in-out;
box-sizing: border-box; box-sizing: border-box;
} }
[data-monster-role=slider].animate {
transition: all 0.3s ease-in-out;
}
[data-monster-role=slider].grabbing { [data-monster-role=slider].grabbing {
cursor: grabbing; cursor: grabbing;
user-select: none; user-select: none;
...@@ -36,7 +40,7 @@ ...@@ -36,7 +40,7 @@
} }
::slotted(div) { ::slotted(div) {
flex: 0 0 100%; flex: 0 0 var(--monster-slides-width);
height: 100%; height: 100%;
display: flex; display: flex;
box-sizing: border-box; box-sizing: border-box;
......
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment