Implement a photo carousel
The layout of this is all CSS with scroll-snap. (Neat!) Also implement a JavaScript scroll event listener that detects when photos scroll and updates the caption and photo data. Refactor a few parts of the photo_exif_table into partials so they can be reused for the carousel items.
This commit is contained in:
parent
0010e65428
commit
f5fd358922
7 changed files with 393 additions and 21 deletions
264
assets/scripts/photo_carousel.js
Normal file
264
assets/scripts/photo_carousel.js
Normal file
|
@ -0,0 +1,264 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
class Carousel {
|
||||
carousel = null;
|
||||
|
||||
constructor(element) {
|
||||
this.carousel = element;
|
||||
}
|
||||
}
|
||||
|
||||
class PhotoParametersTable {
|
||||
tableElement;
|
||||
|
||||
#makeModelElement;
|
||||
#latitudeElement;
|
||||
#longitudeElement;
|
||||
#megapixelsElement;
|
||||
#widthElement;
|
||||
#heightElement;
|
||||
#isoElement;
|
||||
#focalLengthElement;
|
||||
#fNumberElement;
|
||||
#exposureTimeElement;
|
||||
|
||||
constructor(element) {
|
||||
this.tableElement = element;
|
||||
}
|
||||
|
||||
setMakeModel(make, model) {
|
||||
if (!this.#makeModelElement) {
|
||||
this.#makeModelElement = this.#getElementWithQuerySelector("td.make-model");
|
||||
}
|
||||
|
||||
if (!this.#makeModelElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
let makeModel = "";
|
||||
if (make && model) {
|
||||
return `${make} ${model}`;
|
||||
} else if (make) {
|
||||
return make;
|
||||
} else if (model) {
|
||||
return model;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
|
||||
this.#makeModelElement.innerHTML = makeModel;
|
||||
}
|
||||
|
||||
setLocation(latitude, longitude) {
|
||||
let latitudeElement = this.#latitudeElement;
|
||||
if (!latitudeElement) {
|
||||
latitudeElement = this.#getElementWithQuerySelector("td.location > data.latitude");
|
||||
this.#latitudeElement = latitudeElement;
|
||||
}
|
||||
|
||||
let longitudeElement = this.#longitudeElement;
|
||||
if (!longitudeElement) {
|
||||
longitudeElement = this.#getElementWithQuerySelector("td.location > data.longitude");
|
||||
this.#longitudeElement = longitudeElement;
|
||||
}
|
||||
|
||||
if (!latitudeElement || !longitudeElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
latitudeElement.innerHTML = latitude;
|
||||
longitudeElement.innerHTML = longitude;
|
||||
}
|
||||
|
||||
setMegapixels(megapixels) {
|
||||
let megapixelsElement = this.#megapixelsElement;
|
||||
if (!megapixelsElement) {
|
||||
megapixelsElement = this.#getElementWithQuerySelector("td.size > data.megapixels");
|
||||
this.#megapixelsElement = megapixelsElement;
|
||||
}
|
||||
|
||||
if (!megapixelsElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
megapixelsElement.innerHTML = megapixels;
|
||||
}
|
||||
|
||||
setSize(width, height) {
|
||||
let widthElement = this.#widthElement;
|
||||
if (!widthElement) {
|
||||
widthElement = this.#getElementWithQuerySelector("td.size > data.width");
|
||||
this.#widthElement = widthElement;
|
||||
}
|
||||
|
||||
let heightElement = this.#heightElement;
|
||||
if (heightElement) {
|
||||
heightElement = this.#getElementWithQuerySelector("td.size > data.height");
|
||||
this.#heightElement = heightElement;
|
||||
}
|
||||
|
||||
if (!widthElement || !heightElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
widthElement.innerHTML = width;
|
||||
heightElement.innerHTML = height;
|
||||
}
|
||||
|
||||
setISO(iso) {
|
||||
let isoElement = this.#isoElement;
|
||||
if (!isoElement) {
|
||||
isoElement = this.#getElementWithQuerySelector("td.iso");
|
||||
this.#isoElement = isoElement;
|
||||
}
|
||||
|
||||
if (!isoElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
isoElement.innerHTML = `ISO ${iso}`;
|
||||
}
|
||||
|
||||
setFocalLength(focalLength) {
|
||||
let focalLengthElement = this.#focalLengthElement;
|
||||
if (!focalLengthElement) {
|
||||
focalLengthElement = this.#getElementWithQuerySelector("td.focal-length");
|
||||
this.#focalLengthElement = focalLengthElement;
|
||||
}
|
||||
|
||||
if (!focalLengthElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
focalLengthElement.innerHTML = `${focalLength} mm`;
|
||||
}
|
||||
|
||||
setFNumber(f) {
|
||||
let fNumberElement = this.#fNumberElement;
|
||||
if (!fNumberElement) {
|
||||
fNumberElement = this.#getElementWithQuerySelector("td.f-number");
|
||||
this.#fNumberElement = fNumberElement;
|
||||
}
|
||||
|
||||
if (!fNumberElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
fNumberElement.innerHTML = `ƒ${f}`;
|
||||
}
|
||||
|
||||
setExposureTime(exposureTime) {
|
||||
let exposureTimeElement = this.#exposureTimeElement;
|
||||
if (!exposureTimeElement) {
|
||||
exposureTimeElement = this.#getElementWithQuerySelector("td.exposure-time");
|
||||
this.#exposureTimeElement = exposureTimeElement;
|
||||
}
|
||||
|
||||
if (!exposureTimeElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
exposureTimeElement.innerHTML = `${exposureTime} s`;
|
||||
}
|
||||
|
||||
#getElementWithQuerySelector(query) {
|
||||
const element = this.tableElement.querySelector(query);
|
||||
if (!element) {
|
||||
return null;
|
||||
}
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", (event) => {
|
||||
let photoParams = null;
|
||||
|
||||
const photoParamsTableElement = document.querySelector(".photo-params table");
|
||||
if (photoParamsTableElement) {
|
||||
photoParams = new PhotoParametersTable(photoParamsTableElement);
|
||||
}
|
||||
|
||||
document.querySelectorAll("figure.carousel").forEach(carouselElement => {
|
||||
class CarouselItem {
|
||||
element = null;
|
||||
relativeX = 0;
|
||||
isLeftOfContainerCenter = false;
|
||||
|
||||
constructor(element, relativeX) {
|
||||
this.element = element;
|
||||
this.relativeX = relativeX
|
||||
}
|
||||
|
||||
get relativeMaxX() {
|
||||
const rect = this.element.getBoundingClientRect();
|
||||
return this.relativeX + rect.width;
|
||||
}
|
||||
|
||||
get title() { return this.element.dataset.title; }
|
||||
get latitude() { return this.element.dataset.latitude; }
|
||||
get longitude() { return this.element.dataset.longitude; }
|
||||
get megapixels() { return this.element.dataset.megapixels; }
|
||||
get width() { return this.element.dataset.width; }
|
||||
get height() { return this.element.dataset.height; }
|
||||
get make() { return this.element.dataset.make; }
|
||||
get model() { return this.element.dataset.model; }
|
||||
get iso() { return this.element.dataset.iso; }
|
||||
get focalLength() { return this.element.dataset.focalLength; }
|
||||
get fNumber() { return this.element.dataset.fNumber; }
|
||||
get exposureTime() { return this.element.dataset.exposureTime; }
|
||||
}
|
||||
|
||||
let captionElement = carouselElement.querySelector("figcaption");
|
||||
|
||||
let carouselRect = carouselElement.getBoundingClientRect();
|
||||
let carouselRectHorizontalCenter = carouselRect.width / 2.0;
|
||||
|
||||
let carouselItems = Array.from(carouselElement.querySelectorAll("ul > li")).map(item => {
|
||||
let itemRect = item.getClientRects()[0];
|
||||
let relativeX = itemRect.x - carouselRect.x;
|
||||
return new CarouselItem(item, relativeX);
|
||||
});
|
||||
|
||||
let previousScrollLeft = null;
|
||||
let isScrollingLeft = true;
|
||||
|
||||
carouselElement.querySelector("ul").addEventListener("scroll", event => {
|
||||
const target = event.target;
|
||||
const scrollLeft = target.scrollLeft;
|
||||
|
||||
if (previousScrollLeft === null) {
|
||||
carouselRect = target.getBoundingClientRect();
|
||||
carouselRectHorizontalCenter = carouselRect.width / 2.0;
|
||||
}
|
||||
|
||||
if (previousScrollLeft !== null) {
|
||||
isScrollingLeft = scrollLeft > previousScrollLeft;
|
||||
}
|
||||
previousScrollLeft = scrollLeft;
|
||||
|
||||
carouselItems.forEach(item => {
|
||||
const itemRelativeXCoordinate = isScrollingLeft ? item.relativeX - scrollLeft : item.relativeMaxX - scrollLeft;
|
||||
|
||||
const itemWasLeftOfContainerCenter = item.isLeftOfContainerCenter;
|
||||
const itemIsLeftOfContainerCenter = itemRelativeXCoordinate < carouselRectHorizontalCenter;
|
||||
item.isLeftOfContainerCenter = itemIsLeftOfContainerCenter;
|
||||
|
||||
if ( (isScrollingLeft && (!itemWasLeftOfContainerCenter && itemIsLeftOfContainerCenter))
|
||||
|| (!isScrollingLeft && (itemWasLeftOfContainerCenter && !itemIsLeftOfContainerCenter))) {
|
||||
if (captionElement) {
|
||||
captionElement.innerHTML = item.title;
|
||||
}
|
||||
|
||||
photoParams.setMakeModel(item.make, item.model);
|
||||
photoParams.setLocation(item.latitude, item.longitude);
|
||||
photoParams.setMegapixels(item.megapixels);
|
||||
photoParams.setSize(item.width, item.height);
|
||||
photoParams.setISO(item.iso);
|
||||
photoParams.setFocalLength(item.focalLength);
|
||||
photoParams.setFNumber(item.fNumber);
|
||||
photoParams.setExposureTime(item.exposureTime);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue