Change html structure to get get rid of id's

This commit is contained in:
vollstock committed 2026-10-02 17:08:57 +02:00
1 parent 6bd456764e
commit fa97f2c369
4 files changed
+147 -94

No files matched your search

+61 -8
View File
@@ -2,9 +2,11 @@
Snappy is yet another carousel library. Snappy is yet another carousel library.
Its goal is to be as lightweight and performant as possible. Its goal is to be as lightweight and performant as possible.
It does not implement every feature found in other carousel libraries. Instead, it relies on native CSS scroll snapping and adds as little on top as possible. It does not implement every feature found in other carousel libraries. Instead, it relies on native CSS scroll snapping and adds as little on top as possible.
Mouse-drag behavior closely follows native touch scrolling in the browser, including inertia and snapping to a slide. Mouse-drag behavior closely follows native touch scrolling in the browser, including inertia and snapping to a slide. Thus dragging with the mouse feels pretty identical to native scrolling on a touch device or using a trackpad.
| File | Size | | File | Size |
| ---------- | ------: | | ---------- | ------: |
@@ -14,7 +16,8 @@ Mouse-drag behavior closely follows native touch scrolling in the browser, inclu
## Usage ## Usage
### CSS only
### CSS only for a basic scroll container
You can use Snappy without JavaScript. This gives you a simple CSS-only scroll container with snapping slides. You can use Snappy without JavaScript. This gives you a simple CSS-only scroll container with snapping slides.
@@ -23,17 +26,55 @@ You can use Snappy without JavaScript. This gives you a simple CSS-only scroll c
<link rel="stylesheet" href="dist/snappy.css"> <link rel="stylesheet" href="dist/snappy.css">
<!-- add this structure --> <!-- add this structure -->
<div id="demo-carousel" class="snappy-carousel"> <div class="snappy-carousel">
<div class="snappy-slide"></div> <div class="scroll-container">
<div class="snappy-slide"></div> <div class="slide">1</div>
<div class="snappy-slide"></div> <div class="slide">2</div>
... ...
</div>
</div> </div>
``` ```
Snappy is mostly unstyled, leaving the appearance up to you. For inspiration, see the [example](index.html). Snappy is mostly unstyled, leaving the appearance up to you.
For inspiration, see the [complete example](index.html).
### Add JavaScript
### Options
https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Scroll_snap
add data attribites to the carousel container to change the default behavior:
```html
<!--
Options
-------
data-direction="x" (default)
data-direction="y"
data-gap="var(--size-1)"
data-gap="10px"
data-gap="1rem"
data-snap-stop="always"
data-snap-stop="normal" (default)
data-snap-align="center" (default)
data-snap-align="start"
data-snap-align="end"
-->
<div class="snappy-carousel"
data-gap="1rem"
data-snap-align="start">
<div class="scroll-container">
<div class="slide">1</div>
<div class="slide">2</div>
...
</div>
</div>
### Add JavaScript for a carousel
Including the JavaScript adds the following features: Including the JavaScript adds the following features:
@@ -42,6 +83,18 @@ Including the JavaScript adds the following features:
- Mouse dragging - Mouse dragging
```html ```html
<div class="snappy-carousel" data-gap="1rem" data-snap-align="start">
<div class="scroll-container">
<div class="slide">1</div>
<div class="slide">2</div>
...
</div>
<button class="prev">❮</button>
<button class="next">❯</button>
<div class="pagination"></div>
</div>
<script src="dist/snappy.js" defer></script> <script src="dist/snappy.js" defer></script>
``` ```
+25 -33
View File
@@ -30,13 +30,14 @@
margin-bottom: 1em; margin-bottom: 1em;
} }
/* Custom snappy styling */
/* Colors from Open color (https://yeun.github.io/open-color) */
.snappy-carousel { .snappy-carousel {
counter-reset: snappy-slide; .scroll-container {
max-height: 300px; max-height: 300px;
} }
.snappy-slide { .slide {
counter-increment: snappy-slide;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@@ -53,7 +54,7 @@
} }
} }
.snappy-slide:nth-child(even) { .slide:nth-child(even) {
background-color: #e9ecef; background-color: #e9ecef;
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
@@ -62,18 +63,8 @@
} }
} }
.snappy-slide:before { .prev,
content: counter(snappy-slide); .next {
}
.snappy-controls {
display: flex;
align-items: center;
gap: 0.25rem;
margin-top: 1rem;
}
.snappy-button {
width: 1.5rem; width: 1.5rem;
height: 1.5rem; height: 1.5rem;
font-size: 1rem; font-size: 1rem;
@@ -97,11 +88,11 @@
} }
} }
.snappy-pagination { .pagination {
display: flex; display: flex;
} }
.snappy-pagination-dot { .pagination .dot {
width: 0.66rem !important; width: 0.66rem !important;
height: 0.66rem !important; height: 0.66rem !important;
border-radius: 0.33rem; border-radius: 0.33rem;
@@ -115,14 +106,15 @@
cursor: pointer; cursor: pointer;
} }
.snappy-pagination-dot:hover { .pagination .dot:hover {
background-color: #adb5bd !important; background-color: #adb5bd !important;
} }
.snappy-pagination-dot.is-active { .pagination .dot.is-active {
background-color: #1971c2 !important; background-color: #1971c2 !important;
width: 2rem !important; width: 2rem !important;
} }
}
</style> </style>
<link rel="stylesheet" href="dist/snappy.css"> <link rel="stylesheet" href="dist/snappy.css">
</head> </head>
@@ -151,21 +143,21 @@
data-snap-align="start" data-snap-align="start"
data-snap-align="end" data-snap-align="end"
--> -->
<div id="demo-carousel" class="snappy-carousel" data-gap="1rem" data-snap-align="start"> <div class="snappy-carousel" data-gap="1rem" data-snap-align="start">
<div class="snappy-slide"></div> <div class="scroll-container">
<div class="snappy-slide"></div> <div class="slide">1</div>
<div class="snappy-slide"></div> <div class="slide">2</div>
<div class="snappy-slide"></div> <div class="slide">3</div>
<div class="snappy-slide"></div> <div class="slide">4</div>
<div class="snappy-slide"></div> <div class="slide">5</div>
<div class="snappy-slide"></div> <div class="slide">6</div>
<div class="snappy-slide"></div> <div class="slide">7</div>
<div class="slide">8</div>
</div> </div>
<div class="snappy-controls"> <button class="prev">❮</button>
<button class="snappy-button snappy-prev" data-carousel="demo-carousel">❮</button> <button class="next">❯</button>
<button class="snappy-button snappy-next" data-carousel="demo-carousel">❯</button> <div class="pagination"></div>
<div class="snappy-pagination" data-carousel="demo-carousel"></div>
</div> </div>
<!-- Javascript is only needed for pagination and buttons --> <!-- Javascript is only needed for pagination and buttons -->
+34 -24
View File
@@ -1,4 +1,6 @@
.snappy-carousel { .snappy-carousel {
.scroll-container {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
overflow-x: auto; overflow-x: auto;
@@ -6,52 +8,60 @@
user-select: none; user-select: none;
cursor: grab; cursor: grab;
scroll-behavior: smooth; scroll-behavior: smooth;
scroll-snap-type: x mandatory;
&[data-direction="y"] {
flex-direction: column;
overflow-x: hidden;
overflow-y: auto;
}
&.is-dragging { &.is-dragging {
scroll-snap-type: none; scroll-snap-type: none;
scroll-behavior: unset; scroll-behavior: unset;
} }
}
scroll-snap-type: x mandatory;
&[data-direction="y"] { .slide {
flex-shrink: 0;
scroll-snap-align: center;
scroll-snap-stop: normal;
}
.pagination .dot {
display: inline-block;
width: 1rem;
height: 1rem;
background-color: silver;
}
/* Options */
&[data-direction="y"] .scroll-container {
flex-direction: column;
overflow-x: hidden;
overflow-y: auto;
}
&[data-direction="y"] .scroll-container {
flex-direction: column; flex-direction: column;
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
scroll-snap-type: y mandatory; scroll-snap-type: y mandatory;
} }
gap: var(--carousel-gap, 0); &[data-gap] {
gap: attr(data-gap type(*), 0); --carousel-gap: attr(data-gap type(*), 0);
.snappy-slide { .scroll-container {
flex-shrink: 0; gap: var(--carousel-gap, 0);
scroll-snap-align: center; }
scroll-snap-stop: normal;
} }
&[data-snap-align="start"] .snappy-slide { &[data-snap-align="start"] .slide {
scroll-snap-align: start; scroll-snap-align: start;
} }
&[data-snap-align="end"] .snappy-slide { &[data-snap-align="end"] .slide {
scroll-snap-align: end; scroll-snap-align: end;
} }
&[data-snap-stop="always"] .snappy-slide { &[data-snap-stop="always"] .slide {
scroll-snap-stop: always; scroll-snap-stop: always;
} }
}
.snappy-pagination-dot {
display: inline-block;
width: 1rem;
height: 1rem;
background-color: silver;
} }
+27 -29
View File
@@ -16,7 +16,7 @@ class Snappy {
class Carousel { class Carousel {
// DOM references // DOM references
element = null; scrollContainer = null;
prevButton = null; prevButton = null;
nextButton = null; nextButton = null;
pagination = null; pagination = null;
@@ -37,24 +37,23 @@ class Carousel {
activeSlideIndex = 0; activeSlideIndex = 0;
constructor(element) { constructor(element) {
this.element = element; this.scrollContainer = element.querySelector('.scroll-container');
this.direction = this.element.dataset.direction === 'y' ? 'y' : 'x'; this.direction = element.dataset.direction === 'y' ? 'y' : 'x';
const gap = element.dataset.gap;
if (gap) this.scrollContainer.style.setProperty('--carousel-gap', gap);
this.slides = this.scrollContainer.querySelectorAll('.slide');
const gap = this.element.dataset.gap; this.prevButton = document.querySelector(`.prev`);
if (gap) this.element.style.setProperty('--carousel-gap', gap); this.nextButton = document.querySelector(`.next`);
this.pagination = document.querySelector(`.pagination`);
this.slides = this.element.querySelectorAll('.snappy-slide'); this.scrollContainer.addEventListener('mousedown', this.startDrag.bind(this));
this.prevButton = document.querySelector(`.snappy-prev[data-carousel="${this.element.id}"]`); this.scrollContainer.addEventListener('mousemove', this.drag.bind(this));
this.nextButton = document.querySelector(`.snappy-next[data-carousel="${this.element.id}"]`); this.scrollContainer.addEventListener('mouseup', this.endDrag.bind(this));
this.pagination = document.querySelector(`.snappy-pagination[data-carousel="${this.element.id}"]`); this.scrollContainer.addEventListener('mouseleave', this.endDrag.bind(this));
this.scrollContainer.addEventListener('touchstart', this.reset.bind(this));
this.element.addEventListener('mousedown', this.startDrag.bind(this)); this.scrollContainer.addEventListener('wheel', this.reset.bind(this));
this.element.addEventListener('mousemove', this.drag.bind(this)); this.scrollContainer.addEventListener('scroll', this.updateActiveSlide.bind(this));
this.element.addEventListener('mouseup', this.endDrag.bind(this));
this.element.addEventListener('mouseleave', this.endDrag.bind(this));
this.element.addEventListener('touchstart', this.reset.bind(this));
this.element.addEventListener('wheel', this.reset.bind(this));
this.element.addEventListener('scroll', this.updateActiveSlide.bind(this));
if (this.prevButton) { if (this.prevButton) {
this.prevButton.addEventListener('click', () => this.prev()); this.prevButton.addEventListener('click', () => this.prev());
@@ -73,7 +72,7 @@ class Carousel {
startDrag(e) { startDrag(e) {
this.isDragging = true; this.isDragging = true;
this.element.classList.add('is-dragging'); // Disable scroll snapping this.scrollContainer.classList.add('is-dragging'); // Disable scroll snapping
this.velocity = 0; this.velocity = 0;
this.lastPosition = undefined; this.lastPosition = undefined;
this.lastTime = undefined; this.lastTime = undefined;
@@ -113,7 +112,6 @@ class Carousel {
} }
const target = this.calculateDragTarget(); const target = this.calculateDragTarget();
const element = this.element;
const distance = target - this.getScrollPosition(); const distance = target - this.getScrollPosition();
this.velocity = -distance * (1 - this.inertia) / 10; this.velocity = -distance * (1 - this.inertia) / 10;
@@ -159,19 +157,19 @@ class Carousel {
} }
getScrollPosition() { getScrollPosition() {
return this.direction === 'y' ? this.element.scrollTop : this.element.scrollLeft; return this.direction === 'y' ? this.scrollContainer.scrollTop : this.scrollContainer.scrollLeft;
} }
setScrollPosition(position) { setScrollPosition(position) {
if (this.direction === 'y') { if (this.direction === 'y') {
this.element.scrollTop = position; this.scrollContainer.scrollTop = position;
} else { } else {
this.element.scrollLeft = position; this.scrollContainer.scrollLeft = position;
} }
} }
calculateDragTarget() { calculateDragTarget() {
const element = this.element; const element = this.scrollContainer;
const isVertical = this.direction === 'y'; const isVertical = this.direction === 'y';
const maxScrollPosition = isVertical const maxScrollPosition = isVertical
? element.scrollHeight - element.clientHeight ? element.scrollHeight - element.clientHeight
@@ -237,7 +235,7 @@ class Carousel {
reset() { reset() {
this.isDragging = false; this.isDragging = false;
this.element.classList.remove('is-dragging'); this.scrollContainer.classList.remove('is-dragging');
this.velocity = 0 this.velocity = 0
} }
@@ -245,7 +243,7 @@ class Carousel {
this.slides.forEach((slide, index) => { this.slides.forEach((slide, index) => {
// Populate the pagination container with dots for each slide // Populate the pagination container with dots for each slide
let div = document.createElement('div'); let div = document.createElement('div');
div.classList.add('snappy-pagination-dot'); div.classList.add('dot');
div.addEventListener('click', () => this.goToSlide(index)); div.addEventListener('click', () => this.goToSlide(index));
this.pagination.appendChild(div); this.pagination.appendChild(div);
@@ -267,11 +265,11 @@ class Carousel {
const marginStart = isVertical ? 'scrollMarginTop' : 'scrollMarginLeft'; const marginStart = isVertical ? 'scrollMarginTop' : 'scrollMarginLeft';
const marginEnd = isVertical ? 'scrollMarginBottom' : 'scrollMarginRight'; const marginEnd = isVertical ? 'scrollMarginBottom' : 'scrollMarginRight';
const cssPixels = value => Number.parseFloat(value) || 0; const cssPixels = value => Number.parseFloat(value) || 0;
const containerRect = this.element.getBoundingClientRect(); const containerRect = this.scrollContainer.getBoundingClientRect();
const containerStyle = getComputedStyle(this.element); const containerStyle = getComputedStyle(this.scrollContainer);
const snapportStartEdge = containerRect[startEdge] + const snapportStartEdge = containerRect[startEdge] +
(isVertical ? this.element.clientTop : this.element.clientLeft); (isVertical ? this.scrollContainer.clientTop : this.scrollContainer.clientLeft);
const clientExtent = isVertical ? this.element.clientHeight : this.element.clientWidth; const clientExtent = isVertical ? this.scrollContainer.clientHeight : this.scrollContainer.clientWidth;
const snapportStart = snapportStartEdge + cssPixels(containerStyle[paddingStart]); const snapportStart = snapportStartEdge + cssPixels(containerStyle[paddingStart]);
const snapportEnd = snapportStartEdge + clientExtent - cssPixels(containerStyle[paddingEnd]); const snapportEnd = snapportStartEdge + clientExtent - cssPixels(containerStyle[paddingEnd]);
const snapportCenter = (snapportStart + snapportEnd) / 2; const snapportCenter = (snapportStart + snapportEnd) / 2;