Merge pull request 'Keep a post inside the window, and stop letterboxing it' (#2) from media-fits-viewport into main
Reviewed-on: #2
This commit is contained in:
+64
-6
@@ -86,7 +86,9 @@ main { max-width: 680px; margin: 0 auto; }
|
|||||||
|
|
||||||
/* ---------- media ---------- */
|
/* ---------- media ---------- */
|
||||||
|
|
||||||
.media { border-block: 1px solid var(--line); background: #000; }
|
/* Card-coloured, not black: whatever space the media does not fill should
|
||||||
|
read as part of the page rather than as a border around it. */
|
||||||
|
.media { border-block: 1px solid var(--line); background: var(--card); }
|
||||||
.post__head + .media, .text + .media { border-top: 1px solid var(--line); }
|
.post__head + .media, .text + .media { border-top: 1px solid var(--line); }
|
||||||
|
|
||||||
.media__bar {
|
.media__bar {
|
||||||
@@ -135,14 +137,21 @@ main { max-width: 680px; margin: 0 auto; }
|
|||||||
place-items: center;
|
place-items: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Sized by its own proportions rather than stretched to the column: a
|
||||||
|
portrait video filling the width is mostly empty space either side of it.
|
||||||
|
The cap keeps any single item within the window. */
|
||||||
.item img, .item video {
|
.item img, .item video {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: auto;
|
||||||
max-height: 78vh;
|
height: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 70vh;
|
||||||
|
max-height: 70dvh;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
background: #000;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item__note {
|
.item__note {
|
||||||
@@ -162,9 +171,31 @@ main { max-width: 680px; margin: 0 auto; }
|
|||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.media[data-view="grid"] .item { aspect-ratio: 1 / 1 !important; cursor: zoom-in; }
|
.media[data-view="grid"] .item {
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
cursor: zoom-in;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Filled absolutely rather than with height: 100%. The cell's height comes
|
||||||
|
* from its aspect-ratio, which a percentage height will not resolve against,
|
||||||
|
* and `aspect-ratio: auto` means "use the image's own" rather than "ignore
|
||||||
|
* ratios" -- between them a portrait thumbnail ends up twice the height of
|
||||||
|
* its cell.
|
||||||
|
*/
|
||||||
.media[data-view="grid"] .item img,
|
.media[data-view="grid"] .item img,
|
||||||
.media[data-view="grid"] .item video { height: 100%; max-height: none; object-fit: cover; }
|
.media[data-view="grid"] .item video {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
max-width: none;
|
||||||
|
max-height: none;
|
||||||
|
min-height: 0;
|
||||||
|
object-fit: cover;
|
||||||
|
aspect-ratio: auto !important;
|
||||||
|
}
|
||||||
.media[data-view="grid"] .item__note { display: none; }
|
.media[data-view="grid"] .item__note { display: none; }
|
||||||
|
|
||||||
/* ---------- footer ---------- */
|
/* ---------- footer ---------- */
|
||||||
@@ -272,3 +303,30 @@ main { max-width: 680px; margin: 0 auto; }
|
|||||||
without hiding the rest. */
|
without hiding the rest. */
|
||||||
background: color-mix(in srgb, var(--accent, #888) 7%, transparent);
|
background: color-mix(in srgb, var(--accent, #888) 7%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------- fitting the window ---------- */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* On a large screen a single post should be readable without scrolling to
|
||||||
|
* find it -- a video especially, which you can full-screen if you want it
|
||||||
|
* bigger.
|
||||||
|
*
|
||||||
|
* The cap is the window less the chrome around the media: header, caption and
|
||||||
|
* footer come to roughly 270-350px on the posts this serves. Deriving it
|
||||||
|
* instead of subtracting a constant is the obvious thing to try and does not
|
||||||
|
* work: the card is sized by its contents, so a percentage height inside it
|
||||||
|
* resolves to auto and clamps nothing.
|
||||||
|
*
|
||||||
|
* A thread is a column of several posts and cannot fit whatever we do, so it
|
||||||
|
* keeps the looser cap above and scrolls.
|
||||||
|
*/
|
||||||
|
@media (min-width: 700px) and (min-height: 560px) {
|
||||||
|
.post:not([data-segments]) .item img,
|
||||||
|
.post:not([data-segments]) .item video {
|
||||||
|
max-height: calc(100vh - 350px);
|
||||||
|
max-height: calc(100dvh - 350px);
|
||||||
|
/* Below that the media would be too small to be worth showing; better to
|
||||||
|
let a very long caption scroll. */
|
||||||
|
min-height: 220px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+9
-2
@@ -4,6 +4,11 @@ import { html, raw, type Raw } from './html.ts';
|
|||||||
import { badge, layout, originalUrlBlock } from './layout.ts';
|
import { badge, layout, originalUrlBlock } from './layout.ts';
|
||||||
import { linkify } from './text.ts';
|
import { linkify } from './text.ts';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put the ratio on the media itself, never on the figure around it. On the
|
||||||
|
* figure, at the full width of the column, a portrait video forces a box
|
||||||
|
* taller than the window and the rest of the post gets pushed off screen.
|
||||||
|
*/
|
||||||
function aspect(item: Media): string {
|
function aspect(item: Media): string {
|
||||||
return item.width && item.height ? `aspect-ratio: ${item.width} / ${item.height};` : '';
|
return item.width && item.height ? `aspect-ratio: ${item.width} / ${item.height};` : '';
|
||||||
}
|
}
|
||||||
@@ -13,11 +18,12 @@ function renderItem(item: Media, index: number): Raw {
|
|||||||
|
|
||||||
if (item.kind === 'video') {
|
if (item.kind === 'video') {
|
||||||
const poster = item.poster ? proxyUrlFor(item.poster) : undefined;
|
const poster = item.poster ? proxyUrlFor(item.poster) : undefined;
|
||||||
return html`<figure class="item item--video" data-index="${index}" style="${aspect(item)}">
|
return html`<figure class="item item--video" data-index="${index}">
|
||||||
<video
|
<video
|
||||||
controls
|
controls
|
||||||
playsinline
|
playsinline
|
||||||
preload="metadata"
|
preload="metadata"
|
||||||
|
style="${aspect(item)}"
|
||||||
${poster ? html`poster="${poster}"` : ''}
|
${poster ? html`poster="${poster}"` : ''}
|
||||||
><source src="${src}"></video>
|
><source src="${src}"></video>
|
||||||
${item.hls
|
${item.hls
|
||||||
@@ -26,10 +32,11 @@ function renderItem(item: Media, index: number): Raw {
|
|||||||
</figure>`;
|
</figure>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return html`<figure class="item item--image" data-index="${index}" style="${aspect(item)}">
|
return html`<figure class="item item--image" data-index="${index}">
|
||||||
<img
|
<img
|
||||||
src="${src}"
|
src="${src}"
|
||||||
alt="${item.alt ?? ''}"
|
alt="${item.alt ?? ''}"
|
||||||
|
style="${aspect(item)}"
|
||||||
loading="${index === 0 ? 'eager' : 'lazy'}"
|
loading="${index === 0 ? 'eager' : 'lazy'}"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user