Files
trpic/web/scripts/load_mixed/load_mixed.js
Hubert Chen 3ffd1accf0 add GenThumbHash config
config:
    add `GenThumbHash` to control thumbhash generate
action:
    auto generate thumbhash when submit link or upload
web/scripts:
    add `alt` for thumbhash image for a11y
web/templates/gallerys:
    add `aria-label` for sort select
    add meta tags for site info
2026-04-22 22:20:39 +08:00

264 lines
6.7 KiB
JavaScript

let gallery;
let sentinel;
let counter;
let countbox;
let select;
let observer;
let offset = 0;
let loading = false;
let hasMore = true;
const urlParams = new URLSearchParams(window.location.search);
let height = urlParams.get("height") || (window.innerWidth > 1600 ? 320 : 220);
let sort = urlParams.get("sort") || "time";
let tags = urlParams.get("tags") || "";
const limit = window.innerWidth > 1600 ? 50 : 20;
function resetGallery() {
imgviewer.resetList();
gallery.replaceChildren();
height = urlParams.get("height") || (window.innerWidth > 1600 ? 320 : 220);
offset = 0;
hasMore = true;
loadMore();
}
async function loadMore() {
if (loading || !hasMore) return;
observer.unobserve(sentinel);
loading = true;
let url;
let fromArtworks = false;
switch (sort) {
case "time":
case "time-down":
case "publish":
case "publish-down":
url = `/api/artworks?offset=${offset}&limit=${limit}&sort=${sort}`
fromArtworks = true;
break;
default:
url = `/api/files?offset=${offset}&limit=${limit}&sort=${sort}`
break;
}
if (tags) url += `&tags=${tags}`
try {
const res = await fetch(url);
const data = await res.json();
switch (res.status) {
case 200:
break;
case 401:
alert("Session expired");
return;
case 500:
if (data.error) {
hasMore = false;
alert(`${data.msg}: ${data.error}`);
return
}
}
counter.textContent = data.total;
if (fromArtworks) {
appendArtworks(data.artworks);
countbox.childNodes[2].nodeValue = " 个作品"
} else {
appendFiles(data.files);
countbox.childNodes[2].nodeValue = " 张图片"
}
hasMore = data.offset + data.limit < data.total;
offset += data.limit;
if (!sort) sort = data.sort;
} catch (err) {
console.error(err);
hasMore = false;
} finally {
loading = false;
requestAnimationFrame(() => {
observer.observe(sentinel);
});
}
}
function appendArtworks(artworks) {
imgviewer.appendArtworks(artworks);
for (const artwork of artworks) {
let index = 0;
for (const file of artwork.edges.files) {
const fragment = document.createDocumentFragment();
const jWidth = file.width * height / file.height;
const jPadding = file.height / file.width * 100;
const wrapper = document.createElement("div");
wrapper.style.width = jWidth + "px";
wrapper.style.flexGrow = jWidth;
const i = document.createElement("i");
i.style.paddingBottom = jPadding + "%";
wrapper.appendChild(i);
const imgs = document.createElement("div");
imgs.id = "imgs";
const img = document.createElement("img");
img.loading = "lazy";
img.decoding = "async";
img.tabIndex = "0";
if (file.thumbed) {
img.src = `/thumbnails/${file.id_str}.jpg`;
img.dataset.highres = `/files/${file.id_str}.${file.ext}`;
} else {
img.src = `/files/${file.id_str}.${file.ext}`;
}
img.alt = `trpic_artwork_${artwork.id}_${file.id_str}`;
img.dataset.id_str = file.id_str;
img.dataset.artwork_id = artwork.id;
imgs.appendChild(img);
if (file.thumbhash) {
const thumb = document.createElement("img");
thumb.id = thumb.alt = "thumbhash";
thumb.src = thumbHashToDataURL(file.thumbhash);
imgs.appendChild(thumb);
img.onload = () => {
img.onload = null;
thumb.style.opacity = 0;
setInterval(() => {
thumb.remove();
},1000)
};
}
wrapper.appendChild(imgs);
imgviewer.bindClickEvents(img);
fragment.appendChild(wrapper);
gallery.appendChild(fragment);
index++;
}
}
}
function appendFiles(files) {
imgviewer.appendFiles(files);
const fragment = document.createDocumentFragment();
for (const image of files) {
const jWidth = image.width * height / image.height;
const jPadding = image.height / image.width * 100;
const wrapper = document.createElement("div");
wrapper.style.width = jWidth + "px";
wrapper.style.flexGrow = jWidth;
const i = document.createElement("i");
i.style.paddingBottom = jPadding + "%";
if (sort == "size" || sort == "size-down") {
const span = document.createElement("span");
const p = document.createElement("p");
p.textContent = `${image.ext} | ${image.thumbed ? "☇ " : ""}${image.size_str}`;
span.appendChild(p);
i.appendChild(span);
}
wrapper.appendChild(i);
const imgs = document.createElement("div");
imgs.id = "imgs";
const img = document.createElement("img");
img.loading = "lazy";
img.decoding = "async";
img.tabIndex = "0";
img.src = image.thumbed ? `/thumbnails/${image.id_str}.jpg` : `/files/${image.id_str}.${image.ext}`;
let artwork = image.edges.artwork;
img.alt = `trpic_artwork_${artwork.id}_${image.id_str}`;
img.dataset.id_str = image.id_str;
img.dataset.artwork_id = artwork.id;
imgs.appendChild(img);
if (image.thumbhash) {
const thumb = document.createElement("img");
thumb.id = thumb.alt = "thumbhash";
thumb.src = thumbHashToDataURL(image.thumbhash);
imgs.appendChild(thumb);
img.onload = () => {
img.onload = null;
thumb.style.opacity = 0;
setInterval(() => {
thumb.remove();
}, 1000)
};
}
wrapper.appendChild(imgs);
imgviewer.bindClickEvents(img);
fragment.appendChild(wrapper);
}
gallery.appendChild(fragment);
}
document.addEventListener("DOMContentLoaded", () => {
gallery = document.getElementById("gallery");
sentinel = document.getElementById("sentinel");
counter = document.getElementById("count");
countbox = document.getElementById("count-box");
select = document.getElementById('sort-select')
select.value = sort
select.addEventListener('change', () => {
urlParams.set("sort", sort = select.value);
history.replaceState(null, null, `/?${urlParams.toString()}`);
resetGallery()
})
observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
loadMore();
}
},
{
root: null,
rootMargin: "800px", // 提前 800px 加载
threshold: 0,
}
);
observer.observe(sentinel);
});
document.addEventListener("error", (e) => {
let target = e.target;
const tagName = target.tagName || "";
const curTimes = Number(target.dataset.retryTimes) || 0;
if (tagName.toLowerCase() === "img") {
if (curTimes >= 5) {
target.alt = "loading failed";
} else {
setTimeout(() => {
target.dataset.retryTimes = curTimes + 1;
target.src = target.src;
}, 10000);
}
} else {
target = null;
}
}, true);