Yeti 的 Media 媒体对象配方:一行 CSS 类实现"图配文"并排布局
【免费下载链接】yetiA CSS-first, native, zero-build layout and styling framework for web designers.项目地址: https://gitcode.com/gh_mirrors/fo/yeti
本文以 Yeti(一个 CSS-first、零构建的布局与样式框架)中的media配方(recipe)为主线,讲解"图片/视频旁边配几行文字"这一经典 UI 形态的实现原理与实战用法。读完你将掌握:如何用单个.media类快速搭建头像配评论、缩略图配商品简介、头像配人物介绍等布局,理解它与sidebar+frame+stack三个基础布局原语之间的组合关系,并能通过data-width、data-ratio、data-gap、data-align、data-side五个属性精确控制其几何表现。
何时使用 media
评论配头像、商品配缩略图、演讲者配证件照——只要是一张图片挨着几行文字的场景,就是这个形态。它的行为很明确:图与文并排展示,文字始终能保住至少一半的宽度;一旦放不下,就自动上下堆叠。
使用上有一条重要的源顺序(source order)约定:
- 图片放在第一位 → 图片在左、文字在右;
- 图片放在最后一位 → 文字在左、图片在右;
- 阅读顺序(DOM 顺序)始终与视觉顺序保持一致,键盘与屏幕阅读器用户读到的顺序就是源码顺序。
另一条硬性约束:文字内容(body)不能把img、video或picture作为直接子元素,否则它会被识别成第二个"图",破坏"恰好两个子元素"的结构契约(这一点在 src/recipes/media/manifest.json 的 children 声明中也有明确说明)。
从基础原语构建:sidebar + frame + stack
Yeti 的配方哲学是"快捷方式,而非新发明"。media的几何效果完全等价于三个你已经熟悉的布局原语拼在一起:
sidebar:让第一个子元素保持固定宽度,第二个子元素吃下剩余空间,并在空间不足时堆叠;frame:按固定宽高比裁切图片(object-fit: cover);stack:纵向排布文字块,用gap控制文字之间的节奏。
组合形式如下(即 src/recipes/media/docs.md 中给出的完整示例):
<div class="sidebar"><div class="media"><!-- 图片在源码第一位,但被推到右侧显示 --> <div class="media">.media:not([data-gap]) { --_yeti-gap: var(--yeti-space-md); } .media:not([data-width]) { --_yeti-width: var(--yeti-width-xs); } .media:not([data-align]) { --_yeti-align: stretch; } .media:not([data-ratio]) { --_yeti-aspect: 1 / 1; }这些私有变量是 manifest 中列出的内部 token(--_yeti-gap、--_yeti-width、--_yeti-align、--_yeti-aspect),可能在 minor 版本之间变化,不建议在项目代码中直接依赖;公开 token 则是--yeti-width-xs(默认图片宽度)、--yeti-space-md(默认间距)、--yeti-space-sm(文字块内部子元素间距)。
核心机制:如何识别"图"
media不需要你额外包裹一层<figure>,它通过选择器自动识别"图":
.media > :is(img, video, picture), .media > :has(> :is(img, video, picture)) { flex-basis: var(--_yeti-width); flex-grow: 1; aspect-ratio: var(--_yeti-aspect); overflow: hidden; }也就是说,直接子元素只要是img/video/picture三者之一,或者包裹着其中之一的任意元素(利用:has()),就会被当作图(figure);其余子元素自动归入"文字主体"。figure 被裁切到设定比例,object-fit: cover保证图片填满而不变形。
picture 包裹的填充细节
当图片外面既有普通元素、又有picture时(即"包裹的 figure"场景),外层规则设置的100%尺寸对picture内部的img并不自动生效——picture默认是块级元素,其高度若不显式设为 100%,img的百分比高度就没有可解析的参照,最终回退为按内容撑开的 auto 高度。因此 media.css 第 30-34 行专门追加了一组规则,把picture本身及其内的img/video/picture全部强制为inline-size: 100%; block-size: 100%,让填充规则穿透两层包裹。test/browser/recipes/media.spec.js 的第四个测试用例(#w-figure与#w-figure img矩形几乎完全相等)正是对这一情形的回归验证。
带图注的 figure
如果图片外面包一层带<figcaption>的<figure>,media 会把裁切范围限定在图片本身,图注完整地排在图片下方:
<div class="media"> <figure> <img src="hills.jpg" alt="A ridgeline at dawn"> <figcaption>A view worth the early start.</figcaption> </figure> <div> <h3>Weekend in the hills</h3> <p>Six miles, one summit.</p> </div> </div>对应实现见 media.css 第 37-50 行:figure:has(> figcaption)改为纵向 flex、aspect-ratio: auto与overflow: visible,仅让图片保持比例,图注使用--yeti-text-sm与--yeti-color-text-muted呈现为次要文字。
文字主体:吃下剩余空间并保底一半
主体部分的选择器是"图"选择器的补集:
.media > :not(:is(img, video, picture), :has(> :is(img, video, picture))) { display: flex; flex-direction: column; gap: var(--yeti-space-sm); flex-basis: 0; flex-grow: 999; min-inline-size: 50%; }flex-grow: 999让主体吃掉所有剩余宽度(与 sidebar 的 content 子元素策略一致);min-inline-size: 50%保证文字区域在任何情况下都至少占一半宽度,一旦空间不足,flex-wrap: wrap便触发堆叠;- 主体内部按
--yeti-space-sm的间隙纵向排列,子元素 margin 被清零(.media > * { margin: 0 }与主体内> * { margin: 0 }双重保证),文字排版完全交给 flex 间隙。
测试与质量保证
test/browser/recipes/media.spec.js 使用 Playwright 驱动真实浏览器,覆盖以下行为(fixture 见 test/browser/fixtures/recipes/media.html):
- 几何等价:在 1000px 宽视口下,单类形式与组合形式的 figure/body 矩形逐项相等,且 figure 宽度接近
--yeti-width-xs、宽高比接近 1; - 堆叠行为:在 400px 宽视口下,主体落在 figure 下方(
cb.top >= cf.bottom); - 源顺序与 contenteditable="false">【免费下载链接】yetiA CSS-first, native, zero-build layout and styling framework for web designers.
项目地址: https://gitcode.com/gh_mirrors/fo/yeti
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考