Static HTML/CSS/JS portfolio for the design studio 2RE03: locally vendored Bootstrap 5.3.3, self-hosted Inter + Space Grotesk fonts, six pages, Collins-style scroll-snap carousels, a case-studies index with 10 detail pages, and a Web3Forms contact form.
156 lines
4.3 KiB
JavaScript
156 lines
4.3 KiB
JavaScript
/* ==========================================================================
|
|
2RE03 carousel
|
|
Collins-style horizontal scroll-snap gallery.
|
|
|
|
The scrolling itself is native CSS (scroll-snap) + touch/trackpad, so it
|
|
works without JavaScript. This script adds:
|
|
- prev/next buttons (disabled at the ends)
|
|
- keyboard arrow support (when the track is focused)
|
|
- drag-to-scroll for mouse/pointer devices
|
|
========================================================================== */
|
|
(function () {
|
|
"use strict";
|
|
|
|
var roots = document.querySelectorAll("[data-carousel]");
|
|
if (!roots.length) {
|
|
return;
|
|
}
|
|
|
|
function gapSize(track) {
|
|
var cs = window.getComputedStyle(track);
|
|
var g = parseFloat(cs.columnGap || cs.gap);
|
|
return isNaN(g) ? 0 : g;
|
|
}
|
|
|
|
Array.prototype.forEach.call(roots, function (root) {
|
|
var track = root.querySelector("[data-carousel-track]");
|
|
if (!track) {
|
|
return;
|
|
}
|
|
var prev = root.querySelector("[data-carousel-prev]");
|
|
var next = root.querySelector("[data-carousel-next]");
|
|
|
|
function step() {
|
|
var item = track.querySelector(".carousel__item");
|
|
if (!item) {
|
|
return track.clientWidth * 0.8;
|
|
}
|
|
return item.getBoundingClientRect().width + gapSize(track);
|
|
}
|
|
|
|
function move(dir) {
|
|
track.scrollBy({ left: dir * step(), behavior: "smooth" });
|
|
}
|
|
|
|
if (prev) {
|
|
prev.addEventListener("click", function () { move(-1); });
|
|
}
|
|
if (next) {
|
|
next.addEventListener("click", function () { move(1); });
|
|
}
|
|
|
|
track.addEventListener("keydown", function (event) {
|
|
if (event.key === "ArrowRight") {
|
|
event.preventDefault();
|
|
move(1);
|
|
} else if (event.key === "ArrowLeft") {
|
|
event.preventDefault();
|
|
move(-1);
|
|
}
|
|
});
|
|
|
|
function syncButtons() {
|
|
var max = track.scrollWidth - track.clientWidth - 1;
|
|
if (prev) {
|
|
prev.disabled = track.scrollLeft <= 0;
|
|
}
|
|
if (next) {
|
|
next.disabled = track.scrollLeft >= max;
|
|
}
|
|
}
|
|
|
|
track.addEventListener("scroll", syncButtons, { passive: true });
|
|
window.addEventListener("resize", syncButtons);
|
|
syncButtons();
|
|
|
|
/* --- Drag to scroll (mouse / pen; touch scrolls natively) ------------- */
|
|
var dragging = false;
|
|
var moved = false;
|
|
var startX = 0;
|
|
var startLeft = 0;
|
|
|
|
track.addEventListener("dragstart", function (event) {
|
|
event.preventDefault();
|
|
});
|
|
|
|
track.addEventListener("pointerdown", function (event) {
|
|
if (event.pointerType === "touch") {
|
|
return;
|
|
}
|
|
dragging = true;
|
|
moved = false;
|
|
startX = event.clientX;
|
|
startLeft = track.scrollLeft;
|
|
});
|
|
|
|
track.addEventListener("pointermove", function (event) {
|
|
if (!dragging) {
|
|
return;
|
|
}
|
|
var dx = event.clientX - startX;
|
|
if (!moved && Math.abs(dx) > 6) {
|
|
moved = true;
|
|
track.classList.add("is-dragging");
|
|
try { track.setPointerCapture(event.pointerId); } catch (err) {}
|
|
}
|
|
if (moved) {
|
|
event.preventDefault();
|
|
track.scrollLeft = startLeft - dx;
|
|
}
|
|
});
|
|
|
|
function snapToNearest() {
|
|
var items = track.querySelectorAll(".carousel__item");
|
|
if (!items.length) {
|
|
return;
|
|
}
|
|
var trackLeft = track.getBoundingClientRect().left;
|
|
var best = null;
|
|
var bestDist = Infinity;
|
|
Array.prototype.forEach.call(items, function (item) {
|
|
var dist = Math.abs(item.getBoundingClientRect().left - trackLeft);
|
|
if (dist < bestDist) {
|
|
bestDist = dist;
|
|
best = item;
|
|
}
|
|
});
|
|
if (best) {
|
|
track.scrollBy({ left: best.getBoundingClientRect().left - trackLeft, behavior: "smooth" });
|
|
}
|
|
}
|
|
|
|
function endDrag(event) {
|
|
if (!dragging) {
|
|
return;
|
|
}
|
|
dragging = false;
|
|
track.classList.remove("is-dragging");
|
|
try { track.releasePointerCapture(event.pointerId); } catch (err) {}
|
|
if (moved) {
|
|
snapToNearest();
|
|
}
|
|
}
|
|
|
|
track.addEventListener("pointerup", endDrag);
|
|
track.addEventListener("pointercancel", endDrag);
|
|
|
|
/* Swallow the click that follows a drag (e.g. on linked cards). */
|
|
track.addEventListener("click", function (event) {
|
|
if (moved) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
moved = false;
|
|
}
|
|
}, true);
|
|
});
|
|
})();
|