shopify-drag-carousel 1.0.0 → 1.0.2
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 +24 -0
- package/examples/demo-cdn.html +447 -0
- package/examples/demo.html +442 -0
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -18,6 +18,30 @@ A tiny, zero-dependency behavior layer for smooth mouse and touch drag scrolling
|
|
|
18
18
|
npm install shopify-drag-carousel
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
## Example (developer reference)
|
|
22
|
+
|
|
23
|
+
The package ships two demos that load sample products from [`GET https://api.escuelajs.co/api/v1/products`](https://api.escuelajs.co/docs) and show **manual** + **auto-init** carousels. Card layout/CSS is demo-only; the library only adds scroll behavior.
|
|
24
|
+
|
|
25
|
+
### View on CDN (no clone, no install)
|
|
26
|
+
|
|
27
|
+
After the package is **published to npm**, open this URL in a browser:
|
|
28
|
+
|
|
29
|
+
**https://unpkg.com/shopify-drag-carousel@1/examples/demo-cdn.html**
|
|
30
|
+
|
|
31
|
+
That page loads `index.js` and `style.css` from **unpkg** (`shopify-drag-carousel@1`). Pin a version (e.g. `@1.0.0`) if you need a fixed release.
|
|
32
|
+
|
|
33
|
+
### Run locally (repo or `npm install`)
|
|
34
|
+
|
|
35
|
+
Serve the package root over **http** (not `file://`), then open **`examples/demo.html`** (relative paths to the library).
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm run demo
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
→ **http://localhost:5173/examples/demo.html** (root **`demo.html`** redirects there.)
|
|
42
|
+
|
|
43
|
+
Or: `npm install shopify-drag-carousel`, then `npx serve node_modules/shopify-drag-carousel` and open **`/examples/demo.html`**.
|
|
44
|
+
|
|
21
45
|
## CDN (no build step)
|
|
22
46
|
|
|
23
47
|
Script (global `DragCarousel` on `window`):
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>shopify-drag-carousel — example (CDN)</title>
|
|
7
|
+
<!-- Library from unpkg — no clone or npm install required to try the demo -->
|
|
8
|
+
<link
|
|
9
|
+
rel="stylesheet"
|
|
10
|
+
href="https://unpkg.com/shopify-drag-carousel@1/style.css"
|
|
11
|
+
/>
|
|
12
|
+
<style>
|
|
13
|
+
:root {
|
|
14
|
+
--bg: #f4f2ef;
|
|
15
|
+
--surface: #ffffff;
|
|
16
|
+
--text: #1c1917;
|
|
17
|
+
--muted: #78716c;
|
|
18
|
+
--accent: #0d9488;
|
|
19
|
+
--accent-soft: rgba(13, 148, 136, 0.12);
|
|
20
|
+
--radius: 14px;
|
|
21
|
+
--shadow: 0 4px 20px rgba(28, 25, 23, 0.08);
|
|
22
|
+
--shadow-hover: 0 12px 32px rgba(28, 25, 23, 0.12);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
* {
|
|
26
|
+
box-sizing: border-box;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
body {
|
|
30
|
+
margin: 0;
|
|
31
|
+
min-height: 100vh;
|
|
32
|
+
font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
|
|
33
|
+
color: var(--text);
|
|
34
|
+
background: radial-gradient(1200px 600px at 10% -10%, #e0f2f1 0%, transparent 55%),
|
|
35
|
+
radial-gradient(900px 500px at 100% 0%, #fef3c7 0%, transparent 50%), var(--bg);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.page {
|
|
39
|
+
max-width: 68rem;
|
|
40
|
+
margin: 0 auto;
|
|
41
|
+
padding: 2.5rem 1.25rem 4rem;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.hero {
|
|
45
|
+
text-align: center;
|
|
46
|
+
margin-bottom: 2.5rem;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.hero__eyebrow {
|
|
50
|
+
display: inline-block;
|
|
51
|
+
font-size: 0.75rem;
|
|
52
|
+
font-weight: 600;
|
|
53
|
+
letter-spacing: 0.12em;
|
|
54
|
+
text-transform: uppercase;
|
|
55
|
+
color: var(--accent);
|
|
56
|
+
background: var(--accent-soft);
|
|
57
|
+
padding: 0.35rem 0.75rem;
|
|
58
|
+
border-radius: 999px;
|
|
59
|
+
margin-bottom: 0.75rem;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.hero h1 {
|
|
63
|
+
font-size: clamp(1.5rem, 4vw, 2rem);
|
|
64
|
+
font-weight: 700;
|
|
65
|
+
letter-spacing: -0.02em;
|
|
66
|
+
margin: 0 0 0.5rem;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.hero p {
|
|
70
|
+
margin: 0 auto;
|
|
71
|
+
max-width: 36rem;
|
|
72
|
+
color: var(--muted);
|
|
73
|
+
line-height: 1.55;
|
|
74
|
+
font-size: 0.95rem;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.hero code {
|
|
78
|
+
font-size: 0.85em;
|
|
79
|
+
background: rgba(0, 0, 0, 0.06);
|
|
80
|
+
padding: 0.15rem 0.4rem;
|
|
81
|
+
border-radius: 6px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.section {
|
|
85
|
+
margin-bottom: 2.75rem;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.section__head {
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: flex-end;
|
|
91
|
+
justify-content: space-between;
|
|
92
|
+
gap: 1rem;
|
|
93
|
+
margin-bottom: 1rem;
|
|
94
|
+
flex-wrap: wrap;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.section__title {
|
|
98
|
+
font-size: 1.125rem;
|
|
99
|
+
font-weight: 600;
|
|
100
|
+
margin: 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.section__hint {
|
|
104
|
+
font-size: 0.8rem;
|
|
105
|
+
color: var(--muted);
|
|
106
|
+
margin: 0.25rem 0 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.toolbar {
|
|
110
|
+
display: flex;
|
|
111
|
+
align-items: center;
|
|
112
|
+
gap: 0.5rem;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.toolbar button {
|
|
116
|
+
display: inline-flex;
|
|
117
|
+
align-items: center;
|
|
118
|
+
justify-content: center;
|
|
119
|
+
width: 2.5rem;
|
|
120
|
+
height: 2.5rem;
|
|
121
|
+
border: 1px solid rgba(28, 25, 23, 0.12);
|
|
122
|
+
border-radius: 10px;
|
|
123
|
+
background: var(--surface);
|
|
124
|
+
cursor: pointer;
|
|
125
|
+
color: var(--text);
|
|
126
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
127
|
+
transition: background 0.15s, box-shadow 0.15s, transform 0.15s;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.toolbar button:hover:not(:disabled) {
|
|
131
|
+
background: #fafaf9;
|
|
132
|
+
box-shadow: var(--shadow);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.toolbar button:active:not(:disabled) {
|
|
136
|
+
transform: scale(0.96);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.toolbar button:disabled {
|
|
140
|
+
opacity: 0.35;
|
|
141
|
+
cursor: not-allowed;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* Scroll track: flex row; library adds overflow + drag */
|
|
145
|
+
.scroller {
|
|
146
|
+
display: flex;
|
|
147
|
+
gap: 1rem;
|
|
148
|
+
padding: 0.5rem 0.25rem 1rem;
|
|
149
|
+
scroll-padding-inline: 0.25rem;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.scroller-wrap {
|
|
153
|
+
position: relative;
|
|
154
|
+
border-radius: var(--radius);
|
|
155
|
+
background: linear-gradient(180deg, rgba(255, 255, 255, 0.92), rgba(255, 255, 255, 0.72));
|
|
156
|
+
border: 1px solid rgba(28, 25, 23, 0.06);
|
|
157
|
+
box-shadow: var(--shadow);
|
|
158
|
+
padding: 0.75rem 0.5rem;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.product-card {
|
|
162
|
+
flex: 0 0 15rem;
|
|
163
|
+
min-width: 15rem;
|
|
164
|
+
background: var(--surface);
|
|
165
|
+
border-radius: var(--radius);
|
|
166
|
+
overflow: hidden;
|
|
167
|
+
border: 1px solid rgba(28, 25, 23, 0.06);
|
|
168
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
|
169
|
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.product-card:hover {
|
|
173
|
+
transform: translateY(-3px);
|
|
174
|
+
box-shadow: var(--shadow-hover);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.product-card__media {
|
|
178
|
+
position: relative;
|
|
179
|
+
aspect-ratio: 4 / 3;
|
|
180
|
+
background: linear-gradient(145deg, #e7e5e4, #d6d3d1);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.product-card__media img {
|
|
184
|
+
width: 100%;
|
|
185
|
+
height: 100%;
|
|
186
|
+
object-fit: cover;
|
|
187
|
+
display: block;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.product-card__badge {
|
|
191
|
+
position: absolute;
|
|
192
|
+
top: 0.65rem;
|
|
193
|
+
left: 0.65rem;
|
|
194
|
+
font-size: 0.65rem;
|
|
195
|
+
font-weight: 600;
|
|
196
|
+
text-transform: uppercase;
|
|
197
|
+
letter-spacing: 0.06em;
|
|
198
|
+
color: #fff;
|
|
199
|
+
background: rgba(28, 25, 23, 0.72);
|
|
200
|
+
backdrop-filter: blur(6px);
|
|
201
|
+
padding: 0.25rem 0.5rem;
|
|
202
|
+
border-radius: 6px;
|
|
203
|
+
max-width: calc(100% - 1.3rem);
|
|
204
|
+
white-space: nowrap;
|
|
205
|
+
overflow: hidden;
|
|
206
|
+
text-overflow: ellipsis;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.product-card__body {
|
|
210
|
+
padding: 0.9rem 1rem 1.1rem;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.product-card__title {
|
|
214
|
+
margin: 0 0 0.35rem;
|
|
215
|
+
font-size: 0.9rem;
|
|
216
|
+
font-weight: 600;
|
|
217
|
+
line-height: 1.35;
|
|
218
|
+
display: -webkit-box;
|
|
219
|
+
-webkit-line-clamp: 2;
|
|
220
|
+
-webkit-box-orient: vertical;
|
|
221
|
+
overflow: hidden;
|
|
222
|
+
min-height: 2.4em;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.product-card__desc {
|
|
226
|
+
margin: 0 0 0.65rem;
|
|
227
|
+
font-size: 0.72rem;
|
|
228
|
+
line-height: 1.45;
|
|
229
|
+
color: var(--muted);
|
|
230
|
+
display: -webkit-box;
|
|
231
|
+
-webkit-line-clamp: 2;
|
|
232
|
+
-webkit-box-orient: vertical;
|
|
233
|
+
overflow: hidden;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.product-card__row {
|
|
237
|
+
display: flex;
|
|
238
|
+
align-items: baseline;
|
|
239
|
+
justify-content: space-between;
|
|
240
|
+
gap: 0.5rem;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.product-card__price {
|
|
244
|
+
font-size: 1.05rem;
|
|
245
|
+
font-weight: 700;
|
|
246
|
+
color: var(--accent);
|
|
247
|
+
letter-spacing: -0.02em;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.product-card__id {
|
|
251
|
+
font-size: 0.65rem;
|
|
252
|
+
color: #a8a29e;
|
|
253
|
+
font-variant-numeric: tabular-nums;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
#status {
|
|
257
|
+
margin-top: 1.5rem;
|
|
258
|
+
padding: 0.75rem 1rem;
|
|
259
|
+
border-radius: 10px;
|
|
260
|
+
font-size: 0.85rem;
|
|
261
|
+
background: rgba(255, 255, 255, 0.65);
|
|
262
|
+
border: 1px solid rgba(28, 25, 23, 0.08);
|
|
263
|
+
color: var(--muted);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
#status.is-error {
|
|
267
|
+
color: #b91c1c;
|
|
268
|
+
border-color: rgba(185, 28, 28, 0.25);
|
|
269
|
+
background: #fef2f2;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
#status.is-ok {
|
|
273
|
+
color: #0f766e;
|
|
274
|
+
border-color: rgba(13, 148, 136, 0.25);
|
|
275
|
+
background: #f0fdfa;
|
|
276
|
+
}
|
|
277
|
+
</style>
|
|
278
|
+
</head>
|
|
279
|
+
<body>
|
|
280
|
+
<div class="page">
|
|
281
|
+
<header class="hero">
|
|
282
|
+
<span class="hero__eyebrow">Example</span>
|
|
283
|
+
<h1>shopify-drag-carousel</h1>
|
|
284
|
+
<p>
|
|
285
|
+
Products load from
|
|
286
|
+
<code>GET https://api.escuelajs.co/api/v1/products</code>
|
|
287
|
+
— drag the row or use arrows. The library loads from
|
|
288
|
+
<strong>unpkg</strong> (<code>shopify-drag-carousel@1</code>). For local
|
|
289
|
+
dev, use <code>examples/demo.html</code> in the repo instead.
|
|
290
|
+
</p>
|
|
291
|
+
</header>
|
|
292
|
+
|
|
293
|
+
<section class="section" aria-labelledby="sec-manual">
|
|
294
|
+
<div class="section__head">
|
|
295
|
+
<div>
|
|
296
|
+
<h2 class="section__title" id="sec-manual">Manual init + prev / next</h2>
|
|
297
|
+
<p class="section__hint">
|
|
298
|
+
<code>new DragCarousel('#strip-main', { buttons: { … } })</code>
|
|
299
|
+
</p>
|
|
300
|
+
</div>
|
|
301
|
+
<div class="toolbar">
|
|
302
|
+
<button type="button" id="btn-prev" aria-label="Previous products">←</button>
|
|
303
|
+
<button type="button" id="btn-next" aria-label="Next products">→</button>
|
|
304
|
+
</div>
|
|
305
|
+
</div>
|
|
306
|
+
<div class="scroller-wrap">
|
|
307
|
+
<div id="strip-main" class="scroller"></div>
|
|
308
|
+
</div>
|
|
309
|
+
</section>
|
|
310
|
+
|
|
311
|
+
<section class="section" aria-labelledby="sec-auto">
|
|
312
|
+
<div class="section__head">
|
|
313
|
+
<div>
|
|
314
|
+
<h2 class="section__title" id="sec-auto">Auto-init</h2>
|
|
315
|
+
<p class="section__hint">
|
|
316
|
+
Class <code>.drag-carousel</code> — initialized on
|
|
317
|
+
<code>DOMContentLoaded</code>
|
|
318
|
+
</p>
|
|
319
|
+
</div>
|
|
320
|
+
</div>
|
|
321
|
+
<div class="scroller-wrap">
|
|
322
|
+
<div id="strip-auto" class="drag-carousel scroller"></div>
|
|
323
|
+
</div>
|
|
324
|
+
</section>
|
|
325
|
+
|
|
326
|
+
<p id="status" role="status"></p>
|
|
327
|
+
</div>
|
|
328
|
+
|
|
329
|
+
<script src="https://unpkg.com/shopify-drag-carousel@1/index.js"></script>
|
|
330
|
+
<script>
|
|
331
|
+
const API = "https://api.escuelajs.co/api/v1/products";
|
|
332
|
+
|
|
333
|
+
function truncate(s, n) {
|
|
334
|
+
const t = String(s || "");
|
|
335
|
+
return t.length <= n ? t : t.slice(0, n - 1) + "…";
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function productCard(p) {
|
|
339
|
+
const el = document.createElement("article");
|
|
340
|
+
el.className = "product-card";
|
|
341
|
+
|
|
342
|
+
const media = document.createElement("div");
|
|
343
|
+
media.className = "product-card__media";
|
|
344
|
+
|
|
345
|
+
const img = document.createElement("img");
|
|
346
|
+
img.loading = "lazy";
|
|
347
|
+
const src =
|
|
348
|
+
Array.isArray(p.images) && p.images.length
|
|
349
|
+
? p.images[0]
|
|
350
|
+
: "";
|
|
351
|
+
img.src =
|
|
352
|
+
src ||
|
|
353
|
+
"data:image/svg+xml," +
|
|
354
|
+
encodeURIComponent(
|
|
355
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300"><rect fill="#d6d3d1" width="100%" height="100%"/></svg>'
|
|
356
|
+
);
|
|
357
|
+
const title = String(p.title || "");
|
|
358
|
+
img.alt = title;
|
|
359
|
+
|
|
360
|
+
const badge = document.createElement("span");
|
|
361
|
+
badge.className = "product-card__badge";
|
|
362
|
+
badge.textContent = p.category && p.category.name ? String(p.category.name) : "Product";
|
|
363
|
+
|
|
364
|
+
media.append(img, badge);
|
|
365
|
+
|
|
366
|
+
const body = document.createElement("div");
|
|
367
|
+
body.className = "product-card__body";
|
|
368
|
+
|
|
369
|
+
const h = document.createElement("h3");
|
|
370
|
+
h.className = "product-card__title";
|
|
371
|
+
h.textContent = title;
|
|
372
|
+
|
|
373
|
+
const desc = document.createElement("p");
|
|
374
|
+
desc.className = "product-card__desc";
|
|
375
|
+
desc.textContent = truncate(p.description || "", 120);
|
|
376
|
+
|
|
377
|
+
const row = document.createElement("div");
|
|
378
|
+
row.className = "product-card__row";
|
|
379
|
+
|
|
380
|
+
const price = document.createElement("span");
|
|
381
|
+
price.className = "product-card__price";
|
|
382
|
+
const n = Number(p.price);
|
|
383
|
+
price.textContent = Number.isFinite(n) ? "$" + n.toFixed(2) : "";
|
|
384
|
+
|
|
385
|
+
const id = document.createElement("span");
|
|
386
|
+
id.className = "product-card__id";
|
|
387
|
+
id.textContent = "#" + String(p.id != null ? p.id : "");
|
|
388
|
+
|
|
389
|
+
row.append(price, id);
|
|
390
|
+
body.append(h, desc, row);
|
|
391
|
+
el.append(media, body);
|
|
392
|
+
return el;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function fill(el, list) {
|
|
396
|
+
el.replaceChildren();
|
|
397
|
+
list.forEach((p) => el.appendChild(productCard(p)));
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
(async () => {
|
|
401
|
+
const status = document.getElementById("status");
|
|
402
|
+
|
|
403
|
+
if (typeof DragCarousel === "undefined") {
|
|
404
|
+
status.className = "is-error";
|
|
405
|
+
status.textContent =
|
|
406
|
+
"DragCarousel not found. Check that unpkg could load shopify-drag-carousel@1.";
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
status.textContent = "Loading products…";
|
|
411
|
+
|
|
412
|
+
try {
|
|
413
|
+
const res = await fetch(API);
|
|
414
|
+
if (!res.ok) throw new Error("HTTP " + res.status);
|
|
415
|
+
const all = await res.json();
|
|
416
|
+
if (!Array.isArray(all)) throw new Error("Unexpected JSON");
|
|
417
|
+
|
|
418
|
+
const a = all.slice(0, 12);
|
|
419
|
+
const b = all.slice(12, 24);
|
|
420
|
+
|
|
421
|
+
fill(document.getElementById("strip-main"), a);
|
|
422
|
+
fill(document.getElementById("strip-auto"), b);
|
|
423
|
+
|
|
424
|
+
void new DragCarousel("#strip-main", {
|
|
425
|
+
speed: 1.5,
|
|
426
|
+
buttons: { prev: "#btn-prev", next: "#btn-next" },
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
status.className = "is-ok";
|
|
430
|
+
status.textContent =
|
|
431
|
+
"Loaded " +
|
|
432
|
+
all.length +
|
|
433
|
+
" products (showing " +
|
|
434
|
+
a.length +
|
|
435
|
+
" + " +
|
|
436
|
+
b.length +
|
|
437
|
+
"). Demo loads the package from unpkg — no clone required.";
|
|
438
|
+
} catch (e) {
|
|
439
|
+
status.className = "is-error";
|
|
440
|
+
status.textContent =
|
|
441
|
+
"Could not load the API. Use http:// (e.g. npm run demo), not file://. " +
|
|
442
|
+
(e && e.message ? "(" + e.message + ")" : "");
|
|
443
|
+
}
|
|
444
|
+
})();
|
|
445
|
+
</script>
|
|
446
|
+
</body>
|
|
447
|
+
</html>
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>shopify-drag-carousel — example</title>
|
|
7
|
+
<!-- Library optional CSS (grabbing cursor); behavior also injects base styles -->
|
|
8
|
+
<link rel="stylesheet" href="../style.css" />
|
|
9
|
+
<style>
|
|
10
|
+
:root {
|
|
11
|
+
--bg: #f4f2ef;
|
|
12
|
+
--surface: #ffffff;
|
|
13
|
+
--text: #1c1917;
|
|
14
|
+
--muted: #78716c;
|
|
15
|
+
--accent: #0d9488;
|
|
16
|
+
--accent-soft: rgba(13, 148, 136, 0.12);
|
|
17
|
+
--radius: 14px;
|
|
18
|
+
--shadow: 0 4px 20px rgba(28, 25, 23, 0.08);
|
|
19
|
+
--shadow-hover: 0 12px 32px rgba(28, 25, 23, 0.12);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
* {
|
|
23
|
+
box-sizing: border-box;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
body {
|
|
27
|
+
margin: 0;
|
|
28
|
+
min-height: 100vh;
|
|
29
|
+
font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
|
|
30
|
+
color: var(--text);
|
|
31
|
+
background: radial-gradient(1200px 600px at 10% -10%, #e0f2f1 0%, transparent 55%),
|
|
32
|
+
radial-gradient(900px 500px at 100% 0%, #fef3c7 0%, transparent 50%), var(--bg);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.page {
|
|
36
|
+
max-width: 68rem;
|
|
37
|
+
margin: 0 auto;
|
|
38
|
+
padding: 2.5rem 1.25rem 4rem;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.hero {
|
|
42
|
+
text-align: center;
|
|
43
|
+
margin-bottom: 2.5rem;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.hero__eyebrow {
|
|
47
|
+
display: inline-block;
|
|
48
|
+
font-size: 0.75rem;
|
|
49
|
+
font-weight: 600;
|
|
50
|
+
letter-spacing: 0.12em;
|
|
51
|
+
text-transform: uppercase;
|
|
52
|
+
color: var(--accent);
|
|
53
|
+
background: var(--accent-soft);
|
|
54
|
+
padding: 0.35rem 0.75rem;
|
|
55
|
+
border-radius: 999px;
|
|
56
|
+
margin-bottom: 0.75rem;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.hero h1 {
|
|
60
|
+
font-size: clamp(1.5rem, 4vw, 2rem);
|
|
61
|
+
font-weight: 700;
|
|
62
|
+
letter-spacing: -0.02em;
|
|
63
|
+
margin: 0 0 0.5rem;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.hero p {
|
|
67
|
+
margin: 0 auto;
|
|
68
|
+
max-width: 36rem;
|
|
69
|
+
color: var(--muted);
|
|
70
|
+
line-height: 1.55;
|
|
71
|
+
font-size: 0.95rem;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.hero code {
|
|
75
|
+
font-size: 0.85em;
|
|
76
|
+
background: rgba(0, 0, 0, 0.06);
|
|
77
|
+
padding: 0.15rem 0.4rem;
|
|
78
|
+
border-radius: 6px;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.section {
|
|
82
|
+
margin-bottom: 2.75rem;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.section__head {
|
|
86
|
+
display: flex;
|
|
87
|
+
align-items: flex-end;
|
|
88
|
+
justify-content: space-between;
|
|
89
|
+
gap: 1rem;
|
|
90
|
+
margin-bottom: 1rem;
|
|
91
|
+
flex-wrap: wrap;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.section__title {
|
|
95
|
+
font-size: 1.125rem;
|
|
96
|
+
font-weight: 600;
|
|
97
|
+
margin: 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.section__hint {
|
|
101
|
+
font-size: 0.8rem;
|
|
102
|
+
color: var(--muted);
|
|
103
|
+
margin: 0.25rem 0 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.toolbar {
|
|
107
|
+
display: flex;
|
|
108
|
+
align-items: center;
|
|
109
|
+
gap: 0.5rem;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.toolbar button {
|
|
113
|
+
display: inline-flex;
|
|
114
|
+
align-items: center;
|
|
115
|
+
justify-content: center;
|
|
116
|
+
width: 2.5rem;
|
|
117
|
+
height: 2.5rem;
|
|
118
|
+
border: 1px solid rgba(28, 25, 23, 0.12);
|
|
119
|
+
border-radius: 10px;
|
|
120
|
+
background: var(--surface);
|
|
121
|
+
cursor: pointer;
|
|
122
|
+
color: var(--text);
|
|
123
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
124
|
+
transition: background 0.15s, box-shadow 0.15s, transform 0.15s;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.toolbar button:hover:not(:disabled) {
|
|
128
|
+
background: #fafaf9;
|
|
129
|
+
box-shadow: var(--shadow);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.toolbar button:active:not(:disabled) {
|
|
133
|
+
transform: scale(0.96);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.toolbar button:disabled {
|
|
137
|
+
opacity: 0.35;
|
|
138
|
+
cursor: not-allowed;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* Scroll track: flex row; library adds overflow + drag */
|
|
142
|
+
.scroller {
|
|
143
|
+
display: flex;
|
|
144
|
+
gap: 1rem;
|
|
145
|
+
padding: 0.5rem 0.25rem 1rem;
|
|
146
|
+
scroll-padding-inline: 0.25rem;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.scroller-wrap {
|
|
150
|
+
position: relative;
|
|
151
|
+
border-radius: var(--radius);
|
|
152
|
+
background: linear-gradient(180deg, rgba(255, 255, 255, 0.92), rgba(255, 255, 255, 0.72));
|
|
153
|
+
border: 1px solid rgba(28, 25, 23, 0.06);
|
|
154
|
+
box-shadow: var(--shadow);
|
|
155
|
+
padding: 0.75rem 0.5rem;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.product-card {
|
|
159
|
+
flex: 0 0 15rem;
|
|
160
|
+
min-width: 15rem;
|
|
161
|
+
background: var(--surface);
|
|
162
|
+
border-radius: var(--radius);
|
|
163
|
+
overflow: hidden;
|
|
164
|
+
border: 1px solid rgba(28, 25, 23, 0.06);
|
|
165
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
|
166
|
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.product-card:hover {
|
|
170
|
+
transform: translateY(-3px);
|
|
171
|
+
box-shadow: var(--shadow-hover);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.product-card__media {
|
|
175
|
+
position: relative;
|
|
176
|
+
aspect-ratio: 4 / 3;
|
|
177
|
+
background: linear-gradient(145deg, #e7e5e4, #d6d3d1);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.product-card__media img {
|
|
181
|
+
width: 100%;
|
|
182
|
+
height: 100%;
|
|
183
|
+
object-fit: cover;
|
|
184
|
+
display: block;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.product-card__badge {
|
|
188
|
+
position: absolute;
|
|
189
|
+
top: 0.65rem;
|
|
190
|
+
left: 0.65rem;
|
|
191
|
+
font-size: 0.65rem;
|
|
192
|
+
font-weight: 600;
|
|
193
|
+
text-transform: uppercase;
|
|
194
|
+
letter-spacing: 0.06em;
|
|
195
|
+
color: #fff;
|
|
196
|
+
background: rgba(28, 25, 23, 0.72);
|
|
197
|
+
backdrop-filter: blur(6px);
|
|
198
|
+
padding: 0.25rem 0.5rem;
|
|
199
|
+
border-radius: 6px;
|
|
200
|
+
max-width: calc(100% - 1.3rem);
|
|
201
|
+
white-space: nowrap;
|
|
202
|
+
overflow: hidden;
|
|
203
|
+
text-overflow: ellipsis;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.product-card__body {
|
|
207
|
+
padding: 0.9rem 1rem 1.1rem;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.product-card__title {
|
|
211
|
+
margin: 0 0 0.35rem;
|
|
212
|
+
font-size: 0.9rem;
|
|
213
|
+
font-weight: 600;
|
|
214
|
+
line-height: 1.35;
|
|
215
|
+
display: -webkit-box;
|
|
216
|
+
-webkit-line-clamp: 2;
|
|
217
|
+
-webkit-box-orient: vertical;
|
|
218
|
+
overflow: hidden;
|
|
219
|
+
min-height: 2.4em;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.product-card__desc {
|
|
223
|
+
margin: 0 0 0.65rem;
|
|
224
|
+
font-size: 0.72rem;
|
|
225
|
+
line-height: 1.45;
|
|
226
|
+
color: var(--muted);
|
|
227
|
+
display: -webkit-box;
|
|
228
|
+
-webkit-line-clamp: 2;
|
|
229
|
+
-webkit-box-orient: vertical;
|
|
230
|
+
overflow: hidden;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.product-card__row {
|
|
234
|
+
display: flex;
|
|
235
|
+
align-items: baseline;
|
|
236
|
+
justify-content: space-between;
|
|
237
|
+
gap: 0.5rem;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.product-card__price {
|
|
241
|
+
font-size: 1.05rem;
|
|
242
|
+
font-weight: 700;
|
|
243
|
+
color: var(--accent);
|
|
244
|
+
letter-spacing: -0.02em;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.product-card__id {
|
|
248
|
+
font-size: 0.65rem;
|
|
249
|
+
color: #a8a29e;
|
|
250
|
+
font-variant-numeric: tabular-nums;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
#status {
|
|
254
|
+
margin-top: 1.5rem;
|
|
255
|
+
padding: 0.75rem 1rem;
|
|
256
|
+
border-radius: 10px;
|
|
257
|
+
font-size: 0.85rem;
|
|
258
|
+
background: rgba(255, 255, 255, 0.65);
|
|
259
|
+
border: 1px solid rgba(28, 25, 23, 0.08);
|
|
260
|
+
color: var(--muted);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
#status.is-error {
|
|
264
|
+
color: #b91c1c;
|
|
265
|
+
border-color: rgba(185, 28, 28, 0.25);
|
|
266
|
+
background: #fef2f2;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
#status.is-ok {
|
|
270
|
+
color: #0f766e;
|
|
271
|
+
border-color: rgba(13, 148, 136, 0.25);
|
|
272
|
+
background: #f0fdfa;
|
|
273
|
+
}
|
|
274
|
+
</style>
|
|
275
|
+
</head>
|
|
276
|
+
<body>
|
|
277
|
+
<div class="page">
|
|
278
|
+
<header class="hero">
|
|
279
|
+
<span class="hero__eyebrow">Example</span>
|
|
280
|
+
<h1>shopify-drag-carousel</h1>
|
|
281
|
+
<p>
|
|
282
|
+
Products load from
|
|
283
|
+
<code>GET https://api.escuelajs.co/api/v1/products</code>
|
|
284
|
+
— drag the row or use arrows. This file ships in
|
|
285
|
+
<code>examples/demo.html</code> for developers.
|
|
286
|
+
</p>
|
|
287
|
+
</header>
|
|
288
|
+
|
|
289
|
+
<section class="section" aria-labelledby="sec-manual">
|
|
290
|
+
<div class="section__head">
|
|
291
|
+
<div>
|
|
292
|
+
<h2 class="section__title" id="sec-manual">Manual init + prev / next</h2>
|
|
293
|
+
<p class="section__hint">
|
|
294
|
+
<code>new DragCarousel('#strip-main', { buttons: { … } })</code>
|
|
295
|
+
</p>
|
|
296
|
+
</div>
|
|
297
|
+
<div class="toolbar">
|
|
298
|
+
<button type="button" id="btn-prev" aria-label="Previous products">←</button>
|
|
299
|
+
<button type="button" id="btn-next" aria-label="Next products">→</button>
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
<div class="scroller-wrap">
|
|
303
|
+
<div id="strip-main" class="scroller"></div>
|
|
304
|
+
</div>
|
|
305
|
+
</section>
|
|
306
|
+
|
|
307
|
+
<section class="section" aria-labelledby="sec-auto">
|
|
308
|
+
<div class="section__head">
|
|
309
|
+
<div>
|
|
310
|
+
<h2 class="section__title" id="sec-auto">Auto-init</h2>
|
|
311
|
+
<p class="section__hint">
|
|
312
|
+
Class <code>.drag-carousel</code> — initialized on
|
|
313
|
+
<code>DOMContentLoaded</code>
|
|
314
|
+
</p>
|
|
315
|
+
</div>
|
|
316
|
+
</div>
|
|
317
|
+
<div class="scroller-wrap">
|
|
318
|
+
<div id="strip-auto" class="drag-carousel scroller"></div>
|
|
319
|
+
</div>
|
|
320
|
+
</section>
|
|
321
|
+
|
|
322
|
+
<p id="status" role="status"></p>
|
|
323
|
+
</div>
|
|
324
|
+
|
|
325
|
+
<script src="../index.js"></script>
|
|
326
|
+
<script>
|
|
327
|
+
const API = "https://api.escuelajs.co/api/v1/products";
|
|
328
|
+
|
|
329
|
+
function truncate(s, n) {
|
|
330
|
+
const t = String(s || "");
|
|
331
|
+
return t.length <= n ? t : t.slice(0, n - 1) + "…";
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function productCard(p) {
|
|
335
|
+
const el = document.createElement("article");
|
|
336
|
+
el.className = "product-card";
|
|
337
|
+
|
|
338
|
+
const media = document.createElement("div");
|
|
339
|
+
media.className = "product-card__media";
|
|
340
|
+
|
|
341
|
+
const img = document.createElement("img");
|
|
342
|
+
img.loading = "lazy";
|
|
343
|
+
const src =
|
|
344
|
+
Array.isArray(p.images) && p.images.length
|
|
345
|
+
? p.images[0]
|
|
346
|
+
: "";
|
|
347
|
+
img.src =
|
|
348
|
+
src ||
|
|
349
|
+
"data:image/svg+xml," +
|
|
350
|
+
encodeURIComponent(
|
|
351
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300"><rect fill="#d6d3d1" width="100%" height="100%"/></svg>'
|
|
352
|
+
);
|
|
353
|
+
const title = String(p.title || "");
|
|
354
|
+
img.alt = title;
|
|
355
|
+
|
|
356
|
+
const badge = document.createElement("span");
|
|
357
|
+
badge.className = "product-card__badge";
|
|
358
|
+
badge.textContent = p.category && p.category.name ? String(p.category.name) : "Product";
|
|
359
|
+
|
|
360
|
+
media.append(img, badge);
|
|
361
|
+
|
|
362
|
+
const body = document.createElement("div");
|
|
363
|
+
body.className = "product-card__body";
|
|
364
|
+
|
|
365
|
+
const h = document.createElement("h3");
|
|
366
|
+
h.className = "product-card__title";
|
|
367
|
+
h.textContent = title;
|
|
368
|
+
|
|
369
|
+
const desc = document.createElement("p");
|
|
370
|
+
desc.className = "product-card__desc";
|
|
371
|
+
desc.textContent = truncate(p.description || "", 120);
|
|
372
|
+
|
|
373
|
+
const row = document.createElement("div");
|
|
374
|
+
row.className = "product-card__row";
|
|
375
|
+
|
|
376
|
+
const price = document.createElement("span");
|
|
377
|
+
price.className = "product-card__price";
|
|
378
|
+
const n = Number(p.price);
|
|
379
|
+
price.textContent = Number.isFinite(n) ? "$" + n.toFixed(2) : "";
|
|
380
|
+
|
|
381
|
+
const id = document.createElement("span");
|
|
382
|
+
id.className = "product-card__id";
|
|
383
|
+
id.textContent = "#" + String(p.id != null ? p.id : "");
|
|
384
|
+
|
|
385
|
+
row.append(price, id);
|
|
386
|
+
body.append(h, desc, row);
|
|
387
|
+
el.append(media, body);
|
|
388
|
+
return el;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function fill(el, list) {
|
|
392
|
+
el.replaceChildren();
|
|
393
|
+
list.forEach((p) => el.appendChild(productCard(p)));
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
(async () => {
|
|
397
|
+
const status = document.getElementById("status");
|
|
398
|
+
|
|
399
|
+
if (typeof DragCarousel === "undefined") {
|
|
400
|
+
status.className = "is-error";
|
|
401
|
+
status.textContent = "DragCarousel not found. Check script path to ../index.js";
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
status.textContent = "Loading products…";
|
|
406
|
+
|
|
407
|
+
try {
|
|
408
|
+
const res = await fetch(API);
|
|
409
|
+
if (!res.ok) throw new Error("HTTP " + res.status);
|
|
410
|
+
const all = await res.json();
|
|
411
|
+
if (!Array.isArray(all)) throw new Error("Unexpected JSON");
|
|
412
|
+
|
|
413
|
+
const a = all.slice(0, 12);
|
|
414
|
+
const b = all.slice(12, 24);
|
|
415
|
+
|
|
416
|
+
fill(document.getElementById("strip-main"), a);
|
|
417
|
+
fill(document.getElementById("strip-auto"), b);
|
|
418
|
+
|
|
419
|
+
void new DragCarousel("#strip-main", {
|
|
420
|
+
speed: 1.5,
|
|
421
|
+
buttons: { prev: "#btn-prev", next: "#btn-next" },
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
status.className = "is-ok";
|
|
425
|
+
status.textContent =
|
|
426
|
+
"Loaded " +
|
|
427
|
+
all.length +
|
|
428
|
+
" products (showing " +
|
|
429
|
+
a.length +
|
|
430
|
+
" + " +
|
|
431
|
+
b.length +
|
|
432
|
+
"). Serve this folder from the repo root: npm run demo → /examples/demo.html";
|
|
433
|
+
} catch (e) {
|
|
434
|
+
status.className = "is-error";
|
|
435
|
+
status.textContent =
|
|
436
|
+
"Could not load the API. Use http:// (e.g. npm run demo), not file://. " +
|
|
437
|
+
(e && e.message ? "(" + e.message + ")" : "");
|
|
438
|
+
}
|
|
439
|
+
})();
|
|
440
|
+
</script>
|
|
441
|
+
</body>
|
|
442
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shopify-drag-carousel",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Lightweight drag-scroll for horizontal containers—one class, zero dependencies. Shopify & vanilla JS friendly.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
7
7
|
"index.js",
|
|
8
8
|
"style.css",
|
|
9
|
-
"README.md"
|
|
9
|
+
"README.md",
|
|
10
|
+
"examples/demo.html",
|
|
11
|
+
"examples/demo-cdn.html"
|
|
10
12
|
],
|
|
11
13
|
"scripts": {
|
|
12
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
14
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
15
|
+
"demo": "python3 -m http.server 5173"
|
|
13
16
|
},
|
|
14
17
|
"repository": {
|
|
15
18
|
"type": "git",
|