Change html structure to get get rid of id's
This commit is contained in:
1 parent
6bd456764e
commit
fa97f2c369
4 files changed
+147
-94
No files matched your search
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||||
|
|||||||
Reference in new issue
Block a user