shopify-drag-carousel 1.0.0
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 +110 -0
- package/index.js +231 -0
- package/package.json +34 -0
- package/style.css +19 -0
package/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# shopify-drag-carousel
|
|
2
|
+
|
|
3
|
+
**Add drag-scroll to any horizontal container with one class.**
|
|
4
|
+
|
|
5
|
+
A tiny, zero-dependency behavior layer for smooth mouse and touch drag scrolling. It is **not** a UI kit: it does not dictate layout, spacing, or markup—only scroll behavior.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Auto-init** — Elements with class `drag-carousel` are ready on `DOMContentLoaded`; no duplicate setup per element
|
|
10
|
+
- **Mouse + touch** — Desktop drag and mobile touch drag using native `scrollLeft`
|
|
11
|
+
- **Configurable speed** — Default multiplier `1.5`
|
|
12
|
+
- **Optional prev/next** — Wire buttons with CSS selectors; each click scrolls by one viewport width with smooth behavior
|
|
13
|
+
- **Minimal inline styles** — `overflow-x: auto`, `cursor: grab`, `scroll-behavior: smooth` (plus optional `style.css` for `grabbing`)
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install shopify-drag-carousel
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## CDN (no build step)
|
|
22
|
+
|
|
23
|
+
Script (global `DragCarousel` on `window`):
|
|
24
|
+
|
|
25
|
+
```html
|
|
26
|
+
<script src="https://cdn.jsdelivr.net/npm/shopify-drag-carousel@1/index.js"></script>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Optional CSS for the dragging cursor:
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<link
|
|
33
|
+
rel="stylesheet"
|
|
34
|
+
href="https://cdn.jsdelivr.net/npm/shopify-drag-carousel@1/style.css"
|
|
35
|
+
/>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Pin a major version (`@1`) or an exact version in production.
|
|
39
|
+
|
|
40
|
+
## Shopify (Liquid)
|
|
41
|
+
|
|
42
|
+
Drop the script (and optional stylesheet) in your theme—**theme.liquid** asset tags, or a section’s `{% schema %}` and `{% javascript %}` / `{% stylesheet %}` as you prefer.
|
|
43
|
+
|
|
44
|
+
```liquid
|
|
45
|
+
<div class="drag-carousel">
|
|
46
|
+
{% for product in collections.frontpage.products %}
|
|
47
|
+
<div>{{ product.title }}</div>
|
|
48
|
+
{% endfor %}
|
|
49
|
+
</div>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Give children whatever layout you use (flex row, grid, inline blocks, etc.). The library only enables horizontal scrolling and drag behavior.
|
|
53
|
+
|
|
54
|
+
After **dynamic** updates (e.g. section AJAX), call:
|
|
55
|
+
|
|
56
|
+
```js
|
|
57
|
+
DragCarousel.initAll();
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Vanilla JavaScript
|
|
61
|
+
|
|
62
|
+
**Automatic:** Add class `drag-carousel` to your container; initialization runs when the DOM is ready.
|
|
63
|
+
|
|
64
|
+
**Manual:**
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
const carousel = new DragCarousel('#my-row', {
|
|
68
|
+
speed: 1.5,
|
|
69
|
+
buttons: {
|
|
70
|
+
prev: '.prev-btn',
|
|
71
|
+
next: '.next-btn',
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Node / CommonJS:**
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
const DragCarousel = require('shopify-drag-carousel');
|
|
80
|
+
new DragCarousel('.drag-carousel');
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## API
|
|
84
|
+
|
|
85
|
+
### `new DragCarousel(selectorOrElement, options?)`
|
|
86
|
+
|
|
87
|
+
| Option | Type | Default | Description |
|
|
88
|
+
| --------- | -------- | ------- | ------------------------------------------------ |
|
|
89
|
+
| `speed` | `number` | `1.5` | Multiplier applied to pointer movement vs scroll |
|
|
90
|
+
| `buttons` | `object` | `null` | `{ prev: string, next: string }` CSS selectors |
|
|
91
|
+
|
|
92
|
+
String selectors use `document.querySelector` (first match). For multiple carousels, create one instance per element or rely on **auto-init** + class `drag-carousel`.
|
|
93
|
+
|
|
94
|
+
### `DragCarousel.initAll()`
|
|
95
|
+
|
|
96
|
+
Finds all `.drag-carousel` elements that are not yet initialized and attaches behavior. Use after injecting new HTML.
|
|
97
|
+
|
|
98
|
+
### `DragCarousel.isInitialized(element)`
|
|
99
|
+
|
|
100
|
+
Returns whether the given element already has carousel behavior.
|
|
101
|
+
|
|
102
|
+
## Constraints
|
|
103
|
+
|
|
104
|
+
- No React or other UI framework required
|
|
105
|
+
- No bundler required for browser usage
|
|
106
|
+
- Intended to run directly in the browser via script tag or small bundles
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
MIT
|
package/index.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* shopify-drag-carousel — lightweight drag-scroll behavior for horizontal containers.
|
|
3
|
+
* Zero dependencies. Works in Liquid, vanilla JS, or any stack without a bundler.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const initialized = new WeakSet();
|
|
7
|
+
|
|
8
|
+
class DragCarousel {
|
|
9
|
+
/**
|
|
10
|
+
* @param {string | Element | null} selectorOrElement - CSS selector or DOM element
|
|
11
|
+
* @param {{ speed?: number, buttons?: { prev?: string, next?: string } | null }} [options]
|
|
12
|
+
*/
|
|
13
|
+
constructor(selectorOrElement, options = {}) {
|
|
14
|
+
this.element =
|
|
15
|
+
typeof selectorOrElement === 'string'
|
|
16
|
+
? document.querySelector(selectorOrElement)
|
|
17
|
+
: selectorOrElement;
|
|
18
|
+
|
|
19
|
+
this.options = {
|
|
20
|
+
speed: 1.5,
|
|
21
|
+
buttons: null,
|
|
22
|
+
...options,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** @private */
|
|
26
|
+
this._dragging = false;
|
|
27
|
+
/** @private */
|
|
28
|
+
this._startX = 0;
|
|
29
|
+
/** @private */
|
|
30
|
+
this._startScroll = 0;
|
|
31
|
+
/** @private */
|
|
32
|
+
this._onMouseDown = null;
|
|
33
|
+
/** @private */
|
|
34
|
+
this._onTouchStart = null;
|
|
35
|
+
/** @private */
|
|
36
|
+
this._boundMove = null;
|
|
37
|
+
/** @private */
|
|
38
|
+
this._boundStop = null;
|
|
39
|
+
/** @private */
|
|
40
|
+
this._boundTouchMove = null;
|
|
41
|
+
/** @private */
|
|
42
|
+
this._boundTouchEnd = null;
|
|
43
|
+
|
|
44
|
+
if (!this.element || initialized.has(this.element)) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.init();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
init() {
|
|
52
|
+
if (!this.element || initialized.has(this.element)) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
initialized.add(this.element);
|
|
56
|
+
this.applyStyles();
|
|
57
|
+
this.bindEvents();
|
|
58
|
+
if (this.options.buttons) {
|
|
59
|
+
this.bindButtons();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
applyStyles() {
|
|
64
|
+
const el = this.element;
|
|
65
|
+
el.style.overflowX = 'auto';
|
|
66
|
+
el.style.cursor = 'grab';
|
|
67
|
+
el.style.scrollBehavior = DragCarousel.scrollBehaviorOption();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Use smooth scrolling unless the user prefers reduced motion.
|
|
72
|
+
* @returns {'smooth' | 'auto'}
|
|
73
|
+
*/
|
|
74
|
+
static scrollBehaviorOption() {
|
|
75
|
+
if (typeof matchMedia === 'undefined') return 'smooth';
|
|
76
|
+
return matchMedia('(prefers-reduced-motion: reduce)').matches ? 'auto' : 'smooth';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
bindEvents() {
|
|
80
|
+
this._onMouseDown = (e) => {
|
|
81
|
+
if (e.button !== 0) return;
|
|
82
|
+
e.preventDefault();
|
|
83
|
+
this.stopMouse();
|
|
84
|
+
this.start(e.clientX);
|
|
85
|
+
this._boundMove = (ev) => this.move(ev.clientX);
|
|
86
|
+
this._boundStop = () => this.stopMouse();
|
|
87
|
+
window.addEventListener('mousemove', this._boundMove, { passive: true });
|
|
88
|
+
window.addEventListener('mouseup', this._boundStop, { passive: true });
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
this._onTouchStart = (e) => {
|
|
92
|
+
if (!e.touches.length) return;
|
|
93
|
+
this.stopTouch();
|
|
94
|
+
this.start(e.touches[0].clientX);
|
|
95
|
+
this._boundTouchMove = (ev) => {
|
|
96
|
+
if (!this._dragging) return;
|
|
97
|
+
if (!ev.touches.length) return;
|
|
98
|
+
ev.preventDefault();
|
|
99
|
+
this.move(ev.touches[0].clientX);
|
|
100
|
+
};
|
|
101
|
+
this._boundTouchEnd = () => this.stopTouch();
|
|
102
|
+
window.addEventListener('touchmove', this._boundTouchMove, { passive: false });
|
|
103
|
+
window.addEventListener('touchend', this._boundTouchEnd, { passive: true });
|
|
104
|
+
window.addEventListener('touchcancel', this._boundTouchEnd, { passive: true });
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
this.element.addEventListener('mousedown', this._onMouseDown);
|
|
108
|
+
this.element.addEventListener('touchstart', this._onTouchStart, { passive: true });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @param {number} clientX
|
|
113
|
+
*/
|
|
114
|
+
start(clientX) {
|
|
115
|
+
this._dragging = true;
|
|
116
|
+
this.element.classList.add('dragging');
|
|
117
|
+
// Pointer-driven drag must be instant; CSS smooth would fight 1:1 tracking.
|
|
118
|
+
this.element.style.scrollBehavior = 'auto';
|
|
119
|
+
this._startX = clientX;
|
|
120
|
+
this._startScroll = this.element.scrollLeft;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @param {number} clientX
|
|
125
|
+
*/
|
|
126
|
+
move(clientX) {
|
|
127
|
+
if (!this._dragging) return;
|
|
128
|
+
const delta = clientX - this._startX;
|
|
129
|
+
const left = this._startScroll - delta * this.options.speed;
|
|
130
|
+
this.element.scrollTo({ left, behavior: 'auto' });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
stop() {
|
|
134
|
+
this._dragging = false;
|
|
135
|
+
this.element.classList.remove('dragging');
|
|
136
|
+
this.element.style.scrollBehavior = DragCarousel.scrollBehaviorOption();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
stopMouse() {
|
|
140
|
+
if (this._boundMove) {
|
|
141
|
+
window.removeEventListener('mousemove', this._boundMove);
|
|
142
|
+
}
|
|
143
|
+
if (this._boundStop) {
|
|
144
|
+
window.removeEventListener('mouseup', this._boundStop);
|
|
145
|
+
}
|
|
146
|
+
this._boundMove = null;
|
|
147
|
+
this._boundStop = null;
|
|
148
|
+
this.stop();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
stopTouch() {
|
|
152
|
+
if (this._boundTouchMove) {
|
|
153
|
+
window.removeEventListener('touchmove', this._boundTouchMove);
|
|
154
|
+
}
|
|
155
|
+
if (this._boundTouchEnd) {
|
|
156
|
+
window.removeEventListener('touchend', this._boundTouchEnd);
|
|
157
|
+
window.removeEventListener('touchcancel', this._boundTouchEnd);
|
|
158
|
+
}
|
|
159
|
+
this._boundTouchMove = null;
|
|
160
|
+
this._boundTouchEnd = null;
|
|
161
|
+
this.stop();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
bindButtons() {
|
|
165
|
+
const cfg = this.options.buttons;
|
|
166
|
+
if (!cfg) return;
|
|
167
|
+
|
|
168
|
+
const el = this.element;
|
|
169
|
+
const step = () => el.clientWidth;
|
|
170
|
+
|
|
171
|
+
const behavior = DragCarousel.scrollBehaviorOption();
|
|
172
|
+
|
|
173
|
+
if (cfg.prev) {
|
|
174
|
+
const prevEl = document.querySelector(cfg.prev);
|
|
175
|
+
if (prevEl) {
|
|
176
|
+
prevEl.addEventListener('click', () => {
|
|
177
|
+
el.scrollBy({ left: -step(), behavior });
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (cfg.next) {
|
|
182
|
+
const nextEl = document.querySelector(cfg.next);
|
|
183
|
+
if (nextEl) {
|
|
184
|
+
nextEl.addEventListener('click', () => {
|
|
185
|
+
el.scrollBy({ left: step(), behavior });
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Whether this element was already enhanced.
|
|
193
|
+
* @param {Element} el
|
|
194
|
+
* @returns {boolean}
|
|
195
|
+
*/
|
|
196
|
+
static isInitialized(el) {
|
|
197
|
+
return initialized.has(el);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Scan the document for `.drag-carousel` and initialize any not yet handled.
|
|
202
|
+
* Useful after dynamic content (e.g. Shopify section reloads).
|
|
203
|
+
*/
|
|
204
|
+
static initAll() {
|
|
205
|
+
if (typeof document === 'undefined') return;
|
|
206
|
+
document.querySelectorAll('.drag-carousel').forEach((el) => {
|
|
207
|
+
if (!initialized.has(el)) {
|
|
208
|
+
void new DragCarousel(el);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function autoInit() {
|
|
215
|
+
DragCarousel.initAll();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (typeof document !== 'undefined') {
|
|
219
|
+
if (document.readyState === 'loading') {
|
|
220
|
+
document.addEventListener('DOMContentLoaded', autoInit, { once: true });
|
|
221
|
+
} else {
|
|
222
|
+
autoInit();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
227
|
+
module.exports = DragCarousel;
|
|
228
|
+
}
|
|
229
|
+
if (typeof window !== 'undefined') {
|
|
230
|
+
window.DragCarousel = DragCarousel;
|
|
231
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shopify-drag-carousel",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Lightweight drag-scroll for horizontal containers—one class, zero dependencies. Shopify & vanilla JS friendly.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"index.js",
|
|
8
|
+
"style.css",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/Sahilattar8786/shopify-drag-carousel.git"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"shopify",
|
|
20
|
+
"carousel",
|
|
21
|
+
"drag-scroll",
|
|
22
|
+
"slider",
|
|
23
|
+
"horizontal-scroll",
|
|
24
|
+
"touch",
|
|
25
|
+
"vanilla-js"
|
|
26
|
+
],
|
|
27
|
+
"author": "",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"type": "commonjs",
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/Sahilattar8786/shopify-drag-carousel/issues"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/Sahilattar8786/shopify-drag-carousel#readme"
|
|
34
|
+
}
|
package/style.css
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional stylesheet for shopify-drag-carousel.
|
|
3
|
+
* Behavior also injects minimal inline styles; import this for grabbing cursor while dragging.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
.drag-carousel {
|
|
7
|
+
scroll-behavior: smooth;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
@media (prefers-reduced-motion: reduce) {
|
|
11
|
+
.drag-carousel {
|
|
12
|
+
scroll-behavior: auto;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.drag-carousel.dragging {
|
|
17
|
+
cursor: grabbing;
|
|
18
|
+
user-select: none;
|
|
19
|
+
}
|