aboutsummaryrefslogtreecommitdiffstats
path: root/src/features
diff options
context:
space:
mode:
Diffstat (limited to 'src/features')
-rw-r--r--src/features/messages.js30
1 files changed, 29 insertions, 1 deletions
diff --git a/src/features/messages.js b/src/features/messages.js
index a6f49bb..d5da326 100644
--- a/src/features/messages.js
+++ b/src/features/messages.js
@@ -12267,10 +12267,19 @@ export function messagesFeature(ctx) {
function pagerSlide(it) {
const { ev, url } = it;
const video = pager.kind === 'videos';
+ // A picture flies in as the feed's copy of it (1100px wide, decoded
+ // already if the feed painted it; else the server's, cached at the CDN)
+ // and is sharpened to the original once it comes to rest (pagerSharpen).
+ // Originals as they passed — camera files, several megapixels each —
+ // decoded on the main thread and were uploaded as textures mid-flight,
+ // and the flight stuttered (Adam, 2026-10-16: "a bit of choppiness as
+ // they scroll"). A copy that fails falls back to the original.
+ const quick = !video && !/\.gif(\?|$)/i.test(url) ? (mediaReady.get(url) || {}).thumb || serverPic(url, 'col') || url : url;
const media = video
? h('video', { class: 'mp-media', 'data-src': url, loop: true, playsinline: true, preload: 'none',
poster: videoThumbs.get(url) || posterOf(ev, url) || (videoInfo.get(url) || {}).image || serverThumb(url) })
- : h('img', { class: 'mp-media', 'data-src': url, alt: '', draggable: 'false' });
+ : h('img', { class: 'mp-media', 'data-src': quick, 'data-full': url, alt: '', draggable: 'false', decoding: 'async',
+ onError: (e) => { const m = e.currentTarget; if ((m.getAttribute('src') || '') !== url) { m.setAttribute('data-src', url); m.setAttribute('src', url); } } });
const caption = mediaCaption(ev);
const stop = (fn) => (e) => { e.stopPropagation(); fn(e); };
const slide = h('div', { class: 'mp-slide', 'data-k': mediaKey(it) },
@@ -12393,6 +12402,22 @@ export function messagesFeature(ctx) {
const m = slide.querySelector('.mp-media');
if (m && !m.getAttribute('src')) m.setAttribute('src', m.tagName === 'VIDEO' ? playSrc(m.getAttribute('data-src')) : m.getAttribute('data-src'));
}
+ // The slide at rest gets its picture's original — fetched and decoded
+ // aside first, then swapped in (same box, same aspect: nothing moves) —
+ // a moment after landing, so a quick page past it costs nothing.
+ function pagerSharpen(slide) {
+ const m = slide && slide.querySelector('img.mp-media');
+ const full = m && m.getAttribute('data-full');
+ if (!full || m.getAttribute('src') === full || m._sharp) return;
+ m._sharp = setTimeout(() => {
+ m._sharp = 0;
+ if (!pager || pager.cur !== slide) return;
+ const im = new Image();
+ im.decoding = 'async';
+ im.onload = () => { const go = () => { if (pager && m.isConnected && m.getAttribute('src') !== full) { m.setAttribute('data-src', full); m.setAttribute('src', full); } }; (im.decode ? im.decode().then(go, go) : go()); };
+ im.src = full;
+ }, 300);
+ }
function pagerRelease(slide) {
const m = slide.querySelector('video.mp-media');
if (m && m.getAttribute('src')) { try { m.pause(); m.removeAttribute('src'); m.load(); } catch {} }
@@ -12908,9 +12933,12 @@ export function messagesFeature(ctx) {
if (v && pager.curAt) { const secs = (Date.now() - pager.curAt) / 1000; dwell = dwell ? dwell * 0.7 + secs * 0.3 : secs; }
if (pagerSource() && video) el.classList.remove('show');
}
+ if (pager.cur) pager.cur.classList.remove('mp-cur');
+ slide.classList.add('mp-cur'); // (the one with the layer for a pinch: style.css)
pager.cur = slide;
pager.anchor = slide;
pager.curAt = Date.now();
+ pagerSharpen(slide);
pagerArrows(slide);
pagerPosition();
if (pager.fromFeed) { const row = feedNow().notes.find((x) => x.id === slide._rowId); markSeen(row || slide._ev); }