MoreRSS

site iconZhangXinXu | 张鑫旭修改

出版《CSS选择器世界》,喜欢钓鱼、写作。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

ZhangXinXu | 张鑫旭的 RSS 预览

如何使用CSS判断鼠标从哪个方向进入元素?

2026-04-27 17:35:21

by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=12118
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。

一、且看效果

下面是我使用CSS绘制的一个人脸,如果我希望如果用户鼠标从上面进入,表情变成哭丧脸;从下方进入,变成笑脸,该如何实现?

相关代码如下所示:

<div class="face-x">
  <div class="face"></div>
</div>

CSS相关代码(默认效果代码,不含hover交互处理):

.face {
  width: 200px; height: 200px;
  background: #FFD700; /* 经典亮黄色 */
  border-radius: 50%;
  position: relative;
  border: 4px solid #333;
}

/* 使用伪元素绘制眼睛 */
.face::before {
  content: '';
  position: absolute;
  top: 60px; left: 55px;
  width: 20px; height: 20px;
  background: #333;
  border-radius: 50%;
  /* 用阴影复制出另一只眼,省代码 */
  box-shadow: 70px 0 #333;
}

/* 使用伪元素绘制嘴巴 */
.face::after {
  content: '';
  position: absolute;
  top: 100px;
  left: 60px;
  width: 80px;
  height: 20px;
  border-bottom: 8px solid #333;
}

大家不妨自己思考下,有没有什么思路。🤔🤔

二、实现代码与原理

就像变魔术一样,在揭开谜底之前总觉得高深莫测,实际上也就那么回事。

我们直接看最关键的实现代码:

/* 不同方向hover,显示不同表情 */
.face-x {
  width: fit-content;
  position: relative;
  border-radius: 50%;
}
.face-x::after {
  content: '';
  position: absolute;
  inset: 0;
  bottom: 50%;
  /* background: #0008; */
}
.face-x:has(.face:hover) {
  &::after {
    display: none;
  }
  .face::after {
    border-radius: 0 0 50% 50%;
  }
}
.face-x:hover:not(:has(.face:hover)) {
  &::after {
    bottom: 0;
  }
  .face::after {
    top: 120px;
    border-radius: 0 0 50% 50%;
    scale: 1 -1;
  }
}

原理很简单:

创建一个50%高度的覆盖层覆盖在.face元素上,如果用户的鼠标进入后首先触碰的事这个覆盖层,就会匹配.face-x:hover:not(:has(.face:hover)),如果是从另外一个方向进入,由于没有覆盖层阻挡,就会匹配.face-x:has(.face:hover)

如果匹配了前者,那么迅速将覆盖层的尺寸覆盖整个容器元素;如果匹配了后者,迅速隐藏覆盖层。

这样,两种匹配状态就不会冲突。

就是这么简单!

就是这么简单表情包

三、又是学到了的一天

我发现,这年头,大家学习前端技术的热情越来越低了,不仅是行业中,公司内也是如此。

以前内部小测,少说十几人参与,多的时候几十人,现在么,就那固定的四五个人。

包括社区里面,也明显感觉到没那么活跃了。

时代变了。

不过我还是相信,自身的实力才是在风雨中屹立不倒的根本,所以,我还是会继续学习的。

以不变应万变,不忘初心。

不忘初心配图

本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=12118

(本篇完)

借助mediabunny纯JS实现视频水印、剪裁、合成等功能

2026-04-17 15:22:57

by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=12166
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。

一、推荐使用mediabunny

大约2年前,我更新了大量的音视频处理的文章,不过里面的技术实现大都使用原生代码和WebCodecs API手搓的实现。

因为那个时候,WebCodecs API刚出来,技术还不成熟。

自然而然,就陆续出现了不少基于WebCodecs API封装的音视频处理框架。

经过这些年的发展,有一个媒体工具包异军突起,那就是mediabunny!

项目地址:https://github.com/Vanilagy/mediabunny

mediabubby logo

2个月前,我在微博介绍过的MP4/MOV视频转WebM格式在线工具就使用了此项目。

视频转格式截图

mediabunny的能力不仅仅在于视频格式转换与压缩,添加水印、时长剪裁等都不在话下,本文就通过我跑通的demo给大家看下这类需求该如何实现。

二、给视频添加水印

话不多说,先直接上手体验。

您可以狠狠地点击这里:纯前端实现视频添加水印效果demo

选择视频和需要的水印图片,就可以得到最终的效果了,如下截图所示:

水印合成效果示意

其中,最关键的合成就是下面这部分代码:

let ctx = null;
const conversion = await Conversion.init({
    input,
    output,
    video: {
        process: (sample) => {
            if (!ctx) {
                // 创建canvas
                const canvas = new OffscreenCanvas(
                    sample.displayWidth,
                    sample.displayHeight
                );
                ctx = canvas.getContext('2d');
            }

            ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
            sample.draw(ctx, 0, 0);
            ctx.drawImage(watermark, 80, 80 * watermark.naturalHeight / watermark.naturalWidth);

            return ctx.canvas;
        }
    }
});

使用Conversion.init解码视频的每一帧(sample),然后使用canvas将画面和水印图重新绘制,再返回当前canvas即可。

其实不仅是水印合成,任何画面特效,字幕添加,遮罩,尺寸设置都可以使用此方法实现,原理都是一样的,都是对图像进行处理。

三、Video视频前后剪裁

同样的,先看实现效果。

您可以狠狠地点击这里:纯前端实现视频首尾剪裁demo

选择任意的视频,然后拖选滑竿选择需要的视频片段,点击红色的剪裁按钮,就可以看到被剪裁后的视频了:

剪裁demo示意

实际生产环境,拖拽的应该是缩列图的左右两个小翅膀,这里为了简化使用,使用了LuLu UI的双滑块模拟。

其核心实现代码极为简单:

const input = new Input({
    formats: ALL_FORMATS,
    source: new BlobSource(videoFile),
});
const output = new Output({
    format: new Mp4OutputFormat(), // The format of the file
    target: new BufferTarget(),
});

const eleRange = range.querySelector('input');  
const conversion = await Conversion.init({
    input,
    output,
    trim: {
        start: eleRange.from,
        end: eleRange.to,
    },
});

await conversion.execute();

使用trim参数,指定起止时间就可以了。

完整代码可以访问演示页面。

四、多音频和画面的视频合成

一例胜千言,您可以狠狠地点击这里:纯前端实现画面加音频的视频合成demo

默认提供了字幕、背景图、台词,背景音乐可选,用户可以自己上传,可以合成最终的视频:

视频合成demo截图

我查了下API,mediabunny中似乎缺少AudioBuffer处理方法,当然,也可能有,我自己没找到。

这里的AudioBuffer剪裁和合并用的是我自己之前手搓的方法。

相关源码在页面左侧(移动端在下方)有完整展示,基本上相关的视频合成都可以实现了。

这里提供下核心实现部分:

// 定义一个视频合成输出
const output = new Output({
    format: new Mp4OutputFormat(), 
    target: new BufferTarget(),
});

// 添加视频轨道,画面源自canvas
const videoSource = new CanvasSource(canvas, {
    codec: 'avc',
    bitrate: QUALITY_HIGH,
});
output.addVideoTrack(videoSource);

// 添加音轨
const audioSource = new AudioBufferSource({
    codec: 'aac',
    bitrate: QUALITY_HIGH,
});
output.addAudioTrack(audioSource);

await output.start();

// 获取音频文件
const duration = audioFile.duration;

// 每秒30帧
for (let frame = 0; frame < 30 * duration; frame++) {
    draw(frame);
    await videoSource.add(frame / 30, 1 / 30);
}

// 获取音频的 audioBuffer……(代码略),然后添加
await audioSource.add(audioBuffer);

await output.finalize();

五、广告时间

推荐下我几年前在掘金上更新的人文类课程《技术写作指南》

技术写作指南

既是关于写作,也是关注个人成长!

OK,就说这么多,如果你觉得本文内容对你的工作与学习有所帮助,欢迎转发,点赞!

😉😊😇
🥰😍😘

本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=12166

(本篇完)

务必谨慎使用JS WeakRef弱引用

2026-04-13 16:01:51

by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=12155
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。

一、weak三剑客

JS语言中的弱类型除了本文要介绍的WeakRef,还有WeakMap和WeakSet,其中WeakMap我5年前有介绍过,参见“JS WeakMap应该什么时候使用”。

WeakMap和WeakSet支持很多很多年了,无需顾忌任何兼容性问题,但是WeakRef则是前几年刚支持的特性。

WeakRef的兼容性

三者的本质区别

用一句话说明WeakMap、WeakSet和WeakRef的区别,那就是:

WeakMap 和 WeakSet 是集合类数据结构,弱引用是它们管理成员的方式;WeakRef 是对单个对象的弱引用包装器,让你能主动检查对象是否还活着。

用更加通俗的话解释就是:

  • WeakMap:我想给对象贴标签/存数据,但不阻止它被回收
  • WeakSet:我想记住哪些对象出现过,但不阻止它们被回收
  • WeakRef:我想保留一个对象的引用,但允许它随时被回收,用时再检查

下面案例时刻:

1. WeakMap —— 以对象为键的键值映射

const wm = new WeakMap();
let obj = { name: 'Alice' };

wm.set(obj, '额外数据');   // 键必须是对象
console.log(wm.get(obj));  // '额外数据'

obj = null; // obj 被回收后,WeakMap 中对应的条目也会自动消失

补充说明:

  • 必须是对象(不能是原始值)
  • 值可以是任意类型
  • 当键对象没有其他引用时,键值对会被 GC 自动回收
  • 常用于:DOM 节点关联数据、类的私有数据存储

2. WeakSet —— 对象的弱引用集合

const ws = new WeakSet();
let obj = { id: 1 };

ws.add(obj);              // 只能添加对象
console.log(ws.has(obj)); // true

obj = null; // obj 被回收后,WeakSet 中的条目也自动消失

补充说明:

  • 只能存储对象,不能存原始值
  • 只有 addhasdelete 三个方法
  • 常用于:标记对象是否”已访问”、”已处理”、防止循环引用

3. WeakRef —— 对单个对象的弱引用

let obj = { data: 'heavy resource' };
const ref = new WeakRef(obj);

// 通过 .deref() 获取目标对象
console.log(ref.deref());        // { data: 'heavy resource' }
console.log(ref.deref()?.data);  // 'heavy resource'

obj = null;
// 某次 GC 之后...
console.log(ref.deref()); // undefined(对象已被回收)

要点说明:

  • 包装单个对象,不是集合
  • 通过 .deref() 取回对象,如果已被 GC 回收则返回 undefined
  • 通常搭配 FinalizationRegistry 使用,在对象被回收时执行清理回调
  • 常用于:缓存(对象在则命中缓存,被回收则重新创建)

二、JS WeakRef 弱引用经典案例

列举几个适合使用JS WeakRef的场景,希望可以让大家对WeakRef的作用有更加深刻的理解。

1. 缓存场景

const cache = new Map();

function getCached(key, createFn) {
  const ref = cache.get(key);
  const cached = ref?.deref();

  if (cached) return cached; // 缓存命中

  // 缓存未命中或已被 GC 回收,重新创建
  const newObj = createFn();
  cache.set(key, new WeakRef(newObj));
  return newObj;
}

这里 WeakRef 允许缓存的大对象在内存紧张时被自动回收,避免内存泄漏,个人观点,缓存处理是WeakRef最具代表性的应用场景。

2. DOM 元素不泄漏的临时引用

先看DOM元素删除,但是内存依然占用的例子:

// 典型泄漏场景:DOM 节点已从页面移除,但 JS 仍持有强引用
const detachedNodes = [];

function addItem() {
  const div = document.createElement('div');
  document.body.appendChild(div);
  detachedNodes.push(div); // 强引用
}

function removeItem(div) {
  document.body.removeChild(div);
  // 虽然从 DOM 树移除了,但 detachedNodes 数组还引用着它
  // → GC 无法回收 → 内存泄漏!
}

WeakRef可以解决这个问题:

const nodeRefs = [];

function addItem() {
  const div = document.createElement('div');
  document.body.appendChild(div);
  nodeRefs.push(new WeakRef(div)); // 弱引用,不阻止 GC
}

function doSomethingWithNodes() {
  for (const ref of nodeRefs) {
    const node = ref.deref();
    if (node) {
      // 节点还活着,正常操作
      node.style.color = 'red';
    } else {
      // 节点已被 GC 回收,跳过
    }
  }
}

当 DOM 节点从文档中移除且没有其他强引用时,WeakRef 不会阻止 GC 回收它。

但是,虽然WeakRef有避免DOM内存泄露的能力,但我个人觉得不推荐这么使用,原因后面会解释。

3. 事件监听 + 不阻止 GC

有内存问题的代码示意:

// ❌ 泄漏:闭包持有 DOM 引用,且监听器未清理
function setup() {
  const el = document.getElementById('target');
  window.addEventListener('resize', () => {
    el.style.width = '100%'; // el 被闭包捕获,永远无法回收
  });
}

如果使用WeakRef:

function setup() {
  const ref = new WeakRef(document.getElementById('target'));
  window.addEventListener('resize', () => {
    const el = ref.deref();
    if (!el) return; // 对象没了,直接跳过
    el.style.width = '100%';
  });
}

还是那句话,这里其实使用AbortController主动移出监听器是更好的做法。

4. 组件实例池 / 对象池(复用但不泄漏)

比方说下面这段代码,对象销毁后自动从池子里 “消失”,不用手动清理。

const pool = new Set();

function addToPool(obj) {
  pool.add(new WeakRef(obj));
}

function foreachActive(cb) {
  for (const ref of pool) {
    const obj = ref.deref();
    if (obj) cb(obj);
    else pool.delete(ref);
  }
}

5. 防止循环引用导致的内存泄漏

这个案例中父 ↔ 子互相引用,但是由于使用了WeakRef,打破了强引用链,GC 能正常工作,内存不会泄露。

class Parent {
  constructor() {
    this.child = null;
  }
}

class Child {
  constructor(parent) {
    this.parentRef = new WeakRef(parent); // 弱引用
  }
}

const p = new Parent();
const c = new Child(p);
p.child = c;

OK,虽然上面展示了很多可以使用WeakRef的场景,但是这些处理手段都不推荐(除了第一个场景),只能作为避免内存泄漏的最后手段。

三、谨慎使用WeakRef

为什么要谨慎使用WeakRef?最重要的原因其实也就四个字——“不可预测”!

垃圾收集何时、如何以及是否发生,取决于任何给定JavaScript引擎的实现。你在一个引擎中观察到的任何行为,在另一个引擎中、在同一引擎的另一个版本中,甚至在相同引擎的相同版本但在稍有不同的情况下,都可能会有所不同。

比方说某个内存,你明明想要使用,但是却被回收了,这就很烦人。

我之前开发就遇到过,大数据量的视频buffer数据,最终视频合成的时候莫名丢失了,使得我不得不一开始的时候强引用,当时我也是没有搞懂浏览器引擎的回收机制,根据查阅资料的说法,就是内存不足的时候,某些未被强引用的巨大变量会被回收,即使这个变量最后需要使用。

又比如说,你一位内存已经被清理了,可实际上内存清理工作可能会比预期晚得多才进行,或者根本不会进行。

因为JavaScript 引擎的内存回收机制极为复杂:

  • 即使两个对象同时变得无法访问(例如,由于分代收集),一个对象可能比另一个对象更早地被垃圾收集。
  • 垃圾收集工作可以通过使用增量和并发技术来分时段进行。
  • 可以使用各种运行时启发式算法来平衡内存使用和响应性。
  • JavaScript 引擎可能会保留对看似无法访问的内容的引用(例如,在闭包或内联缓存中)。
  • 不同的JavaScript引擎处理这些事情的方式可能不同,或者同一个引擎在不同版本中可能会改变其算法。
  • 复杂的因素可能导致对象被意外地保留很长时间,例如与某些应用程序编程接口(API)一起使用时。

所以,在绝大多数场景下,主动清理引用(移除监听器、清空变量、useEffect return cleanup)才是正道。

WeakRef 的 deref() 返回 undefined 的时机依赖 GC,不可预测,所以不应该用它来做确定性的资源管理。

例如上面案例提到的事件管理,下面的AbortController方法是更加推荐的:

function setup() {
  const el = document.getElementById('target');
  const controller = new AbortController();
  
  window.addEventListener('resize', () => {
    el.style.width = '100%';
  }, { signal: controller.signal });
  
  // 需要清理时
  controller.abort(); // 自动移除监听器
}

我个人的观点是这样的:你该怎么开发就怎么开发,不用管什么内存没有回收这些,大多数场景下,你多使用点内存,少一点内存,对用户影响几乎没有,让JS引擎自己去维护吧,除非遇到内存占有很大,不得不去处理的场景,这个时候,大家再去考虑使用WeakRef做内存优化。

四、结语碎碎念

我的个人博客开始出现很多AI味十足的评论,先是一句话总结文章内容,然后对内容和作者无脑吹,什么“硬核”,什么“高大上”。

还不如以前回复的“写的狗屁不通”这样的评论。[叹气]

最近刷漫剧,发现下面也有很多这样的评论,都是AI机器人。

其实龙虾火了之后,这样的东西会越来越普遍。

比方说即有同事弄了自动去社交网站评论的龙虾。

不过我目前还未深入这类AI工具,不急不急,对我而言,掌握更基础的知识与技能,比追逐流行工具更有价值。

好了,就说这么多吧,感谢大家的阅读,我们下周再见!

飞吻再见

😉😊😇
🥰😍😘

本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=12155

(本篇完)

CSS六边形头像的实现与蜂巢布局

2026-04-07 16:41:49

by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=12118
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。

本文内容分为两啪,一个是六边形头像效果的实现,而是金字塔布局(又称蜂巢布局)的实现。

一、六边形头像

不啰嗦,直接看代码和最终实现的效果,同样的,用的是CSS corner-shape属性。

img {
  aspect-ratio: cos(30deg);
  border-radius: 50% / 25%;
  corner-shape: bevel;
  width: 150px;
  border: 1px solid #0001;
  object-fit: cover;
}

实时渲染效果如下:

如果你是手机访问,或者一些很久没升级的国产浏览器,应当看不到效果,可以看下面的截图:

六边形头像截图效果

六边形头像的CSS代码是固定的,大家使用的时候直接复制粘贴就好了。

二、蜂窝布局实现方法

六边形也正好是蜂巢格子的形状,因此,非常适合用来实现金字塔一样的蜂窝布局。

实际上,这种布局在日常开发中也是比较常见的,例如我最近开发的某个页面就有这样的布局:

金字塔布局示意

一般的开发人员遇到这种状况,可能会手工硬搓每个元素的定位,例如,例如匹配第一项元素,让其绝对定位居中,第二行元素保持Flex布局。

.item:first-child {
  /* 第一行特殊居中处理 */
  position: absolute;
}

其实可以试试Flex倒序排版。

Flex实现蜂窝布局

假设HTML结构如下:

<div class="container">
  <span>1</span>
  <span>2</span>
  <span>3</span>
</div>

则可以试试如下所示的CSS:

.container {
  --size: 40px;
  --gap: 5px;
  --offset: calc((2 * var(--size) + var(--gap)) / (-4 * cos(30deg)));

  width: 240px;
  display: flex;
  flex-wrap: wrap-reverse;
  direction: rtl;
  justify-content: center;
  gap: var(--gap);
  padding-bottom: calc(-1 * var(--offset));
}

.container > span {
  aspect-ratio: cos(30deg);
  border-radius: 50% / 25%;
  corner-shape: bevel;
  width: calc(var(--size) * 2);
  margin-bottom: var(--offset);
  /* 排序倒序 */
  order: calc(-1 * sibling-index());
  /* 提示文字居中 */
  display: grid;
  place-items: center;
  background-color: deepskyblue;
  color: #fff;
}

此时的渲染效果如下截图所示:

Flex实现的蜂巢布局

不过Flex倒序只适合三个数量,如果超过,那么这个布局方法就无效了。

下面问题来了,有没有什么办法,无论列表数量多少,自动金字塔布局呢?

Grid实现蜂巢布局

有,Grid布局是可以实现这样的效果的。

我们先从最简单三个列表项开始实现,假设HTML代码如下:

<div class="container">
  <s></s>
  <s></s>
  <s></s>
</div>

如下CSS代码就可以有蜂窝布局效果了:

.container {
  --size: 40px;
  --gap: 5px;

  width: 240px;
  display: grid;
  grid-template-columns: repeat(auto-fit, var(--size));
  justify-content: center;
  gap: var(--gap);
  padding-bottom: calc((2 * var(--size) + var(--gap)) / (4 * cos(30deg)));
  outline: 1px dotted;
}

.container > s {
  grid-column-end: span 2;
  aspect-ratio: cos(30deg);
  border-radius: 50% / 25%;
  corner-shape: bevel;
  /* 垂直方向间隙和gap保持一致 */
  margin-bottom: calc((2 * var(--size) + var(--gap)) / (-4 * cos(30deg)));
  background-color: deepskyblue;
}

.container > :nth-child(1) {
  grid-column-start: 3;
}

.container > :nth-child(2) {
  grid-column-start: 2;
}

原理很简单,只需要精确指定每一行第一个元素的grid-column-start值就好了,在Grid布局中,每一行后面的元素只会自动跟随排列的。

如果是三个列表元素,那么第一行的首元素序列是1,因此选择器是:nth-child(1),第二行的首元素序列是2,因此选择器是:nth-child(2),最后一个元素自动跟随,无需专门设置。

实时渲染效果如下:

不限数量全自动蜂巢布局

由于Chrome浏览器支持了if函数,因此,纯CSS实现不限数量全自动蜂巢布局成为了可能,具体实现代码如下:

@property --_n {syntax: "<integer>";initial-value: 1;inherits: true}
@property --_i {syntax: "<number>";initial-value: 1;inherits: true}
@property --_j {syntax: "<number>";initial-value: 1;inherits: true}
@property --_c {syntax: "<number>";initial-value: 1;inherits: true}
@property --_d {syntax: "<number>";initial-value: 1;inherits: true}

.container {
  --s: 40px;  /* 尺寸大小  */
  --g: 5px;   /* 间隙大小 */
  
  display: grid;
  grid-template-columns: repeat(auto-fit, var(--s) var(--s));
  justify-content: center;
  gap: var(--g);
  padding-bottom: calc((2 * var(--s) + var(--g)) / (4 * cos(30deg)));
  container-type: inline-size;
}
.container > * {
  grid-column-end: span 2;
  aspect-ratio: cos(30deg);
  border-radius: 50% / 25%;
  corner-shape: bevel;
  margin-bottom: calc((2 * var(--s) + var(--g)) / (-4 * cos(30deg)));
  --_n: round(down, (100cqw + var(--g)) / (2 * (var(--s) + var(--g))));
  --_i: calc((sibling-index() - 2 + (var(--_n) * (3 - var(--_n))) / 2) / (2 * var(--_n) - 1));
  --_c: mod(var(--_i), 1);
  --_j: calc(sqrt(2 * sibling-index() - 1.75) - .5);
  --_d: mod(var(--_j), 1);
  grid-column-start: 
    if(
      style((--_i >= 1) and (--_c: 0)): 2; 
      style(--_d: 0): max(0, var(--_n) - var(--_j));
    );
}

先是根据容器尺寸和元素尺寸计算每行可以显示的数量,然后根据取模的值是不是整数,判断是不是每一行的第一项,通过if()函数设置精准的grid-column-start值。

原理虽然简单,但是实现细节还是很复杂的,比如大家无需深究,直接复制粘贴代码使用就可以了。

只需要将子元素换成图片元素,就可以轻松实现下图所示的蜂巢头像布局效果。

蜂窝头像布局示意

具体不展开,因为受制于兼容性限制,目前只能实验环境使用。

三、结语说明

前端三剑客中,CSS的发展是最快的,你看我写的新特性介绍文章,大多数都是CSS,并不是我刻意挑选,而真TM就是大多数前端新特性都是CSS。

考虑到CSS的学习热潮早就沉寂多年。

我觉得CSS这门语言离断层不远了,只要几年不关注,我跟大家讲,那些前沿的CSS代码,绝对是看不懂的。

各种新函数、属性还有语法糖层出不穷,就好比本文这个金字塔蜂巢布局中的CSS实现细节,我估计9成以上的前端是看不懂什么意思的。

其中出现的这些特性,我之前都有介绍:

  1. corner-shape见此文:大开眼界的CSS corner-shape属性
  2. aspect-ratio见此文:Chrome 88已经支持aspect-ratio属性了,学起来
  3. round()mod()等数学函数:Chrome也支持round等CSS数学函数了
  4. cos()三角函数见:CSS sin()/cos()等数学三角函数简介与应用
  5. sibling-index()索引序号函数介绍出处:CSS索引和数量匹配函数sibling-index sibling-count简介
  6. if()函数介绍:CSS倒反天罡居然支持if()函数了
  7. container-type100cqw属于容器查询里面的知识:2022年最期待的CSS container容器查询

所以还是那句话,学习是不能停止的,时代变化很快,要是安于现状,说不定就会掉队。

参考文章:响应式金字塔网格

😉😊😇
🥰😍😘

本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=12118

(本篇完)

CSS corner-shape与背景底纹技术

2026-03-30 14:08:36

by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=12115
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。

一、corner-shape之前介绍过

CSS corner-shape属性去年8月份刚刚介绍过,可以实现多种图形效果。

很强,也大开眼界。

有兴趣的可以去这里学习其语法:“大开眼界的CSS corner-shape属性

但是这些图形效果一次只能创建一个,如果可以将这些图形效果批量复制,岂不是可以实现各种复杂的底纹背景效果。

还真可以实现。

二、文字、图形SVG背景技术

这种将HTML内容变成SVG背景图的技术我之前就研究并介绍过,可以参见“如何让文字作为CSS背景图片显示”此文。

对于文字,我们可以使用纯SVG语法。

但是,对于相对有些复杂的图形效果,我们可以借助<foreignObject>元素。

foreignObject我之前也介绍过,可以用来实现DOM截图效果,详见“SVG foreignObject简介与截图等应用”一文。

所以,我们的实现模板就变成了这样:

.template {
  background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg"><foreignObject width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml">对该DIV设置样式</div></foreignObject></svg>'
}

三、实战-从最简单的开始

在过去,我们要实现网格线,需要使用两个repeating-linear-gradient()渐变函数,函数里面也需要写比较精确的断点。

如果,借助corner-shape属性,我们有了更加渐变的实现方法。

CSS代码示意:

.grid-bg {
  aspect-ratio: 1;
  background: #fff url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg"><foreignObject width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="border: 1px solid deepskyblue;border-radius: calc(50% - 1px);corner-shape: notch;aspect-ratio: 1;width:60px;"></div></foreignObject></svg>');
  background-size: 60px 60px;
}

此时,我们给页面添加一个类名是grid-bg的canvas元素,我们就可以看到如下图所示的底纹效果了:

而CSS的背景图是可以无限叠加的,所以,我们可以再网格线基础上再融合点其他图形,例如,闪烁星星,于是:

..grid-bg2 {
  aspect-ratio: 1;
  --url-star: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><foreignObject width='100%' height='100%'><div xmlns='http://www.w3.org/1999/xhtml' style='background:deeppink;width:60px;height:60px;corner-shape:superellipse(-2.5);border-radius:50%;'></div></foreignObject></svg>");
  --url-grid: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg"><foreignObject width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="border: 1px solid deepskyblue;border-radius: calc(50% - .75px);corner-shape: notch;aspect-ratio: 1;width:60px;"></div></foreignObject></svg>');
  background: var(--url-star) 1px 1px, var(--url-grid);
  background-size: 60px 60px;
}

可以有如下图所示的渲染效果:

底纹图形效果示意

更多案例

这里有个codepen地址,里面有多个使用corner-shape属性实现的背景纹理图。

背景纹理图

原理都是一样的,我就不赘述了。

四、结语说明

如果遇到SVG图形直接访问是可以的,但是作为background图片就无法渲染,试试对其进行转义,代码如下:

const encodeSvg = function (str) {
    return "data:image/svg+xml," + str.replace(/"/g,"'").replace(/%/g,"%25").replace(/#/g,"%23").replace(/{/g,"%7B").replace(/}/g,"%7D").replace(/</g,"%3C").replace(/>/g,"%3E");
}

其中 str就是完整的SVG代码。

另外,需要注意的是,使用<foreignObject>元素作为SVG背景图形的时候,里面的HTML祖先元素需要设置xhtml的命名空间,外部的SVG元素也需要SVG的命名空间,否则会有渲染问题。

截止到今天,也就是2026年3月30日,corner-shape属性依然只有Chrome浏览器支持,所以,本技术,大家目前了解下即可,实际生产环境使用,还需要些时日。

corner-shape的兼容性

😉😊😇
🥰😍😘

本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=12115

(本篇完)