MoreRSS

site iconZhangXinXu | 张鑫旭修改

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

Inoreader Feedly Follow Feedbin Local Reader

ZhangXinXu | 张鑫旭的 RSS 预览

光标的形状也能设置了,就是CSS caret-shape属性

2026-05-18 15:50:42

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

一、温故知新:光标颜色

2018年的时候,分享过CSS改变光标颜色的caret-color属性,详见“CSS改变光标颜色caret-color简介及其它变色方法”一文。

现在,除了光标的颜色,光标的形状也能改变了,这个CSS属性就是caret-shape属性。

caret-shape: auto;
caret-shape: bar;
caret-shape: block;
caret-shape: underscore;

二、caret-shape效果一览

属性值bar的效果就是目前我们常见的效果,一根1像素的竖条条像小星星一样闪啊闪的。

这个不不放图。

block的光标效果是一个方块闪啊闪的,效果如下GIF动图:

block光标效果示意

当我们输入的字符会覆盖下一个字符的时候,就适合使用caret-shape:block块状光标。

underscore是下划线光标效果。GIF录屏效果参见:

下划线光标

当我们需要模仿打字机或下划线文本输入样式效果的时候,那就可以使用caret-shape:underscore下划线插入符号光标。

三、caret-animation属性是?

caret-shape属性同时出现的还有个CSS属性,名为caret-animation,用来控制光标闪烁与否的。

语法如下:

/* 动感光波,闪闪闪 */
caret-animation: auto;
/* 糟了,中了定身咒 */
caret-animation: manual;

当我们希望自定义光标闪烁效果的时候,就可以用到caret-animation:manual,例如下面这个案例,七色光标变色闪烁效果。

<input placeholder="focus me" class="custom-caret" />

CSS代码如下:

@keyframes custom-blink {
  0%, 50% { caret-color: transparent; }
  50%, 100% { caret-color: hsl(calc(3.6 * var(--seed)), 100%, 50%); }
}

@property --seed {
  syntax: "<integer>";
  inherits: true;
  initial-value: 0;
}

@keyframes seed {
  from { --seed: 0; }
  to { --seed: 100; }
}

.custom-caret {
  caret-animation: manual; /* 关闭默认的光标闪烁 */
  caret-color: blue;
  animation: custom-blink 1.5s infinite step-end, seed calc(1.5s * 7) both infinite linear;
}

眼见为实,您可以狠狠地点击这里:CSS自定义caret光标七色动画demo

由于光标比较细,其实效果没那么明显,如下GIF动图。

光标变色动画效果

四、caret缩写属性

caret-colorcaret-animationcaret-shape可以缩写为caret属性。

caret: red;
caret: block;
caret: manual;

/* 两个值 */
caret: red manual;
caret: block auto;
caret: underscore orange;

/* 三个值 */
caret: bar manual red; 
caret: block auto #00ff00;

由于三个属性的值类型都不一样,所以缩写属性数量任意,顺序也没有什么要求,一眼就看出什么意思。

不过日常开发不建议缩写,因为兼容性不一致。

caret-color很早就支持,但是你如果使用caret缩写属性设置光标颜色,本来支持的浏览器反而不支持。

所以,caret属性大家暂时先了解了解就可以了。

五、兼容性and结语么么😘

兼容性么,咳咳,暂时仅Chrome浏览器支持,还在这是个渐进增强特性,浏览器不支持,也就是默认效果,不影响在实际开发中的使用。

caret-shape兼容性

结语了,该说些什么呢。

现在年纪上去了,没有多少分享欲了。

算了,不扯淡了,就这样吧。

动感光波

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

(本篇完)

pretext与文字四面环绕效果的实现

2026-05-11 11:53:06

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

一、pretext项目简介

特意看了下,这篇文章标题写好的时候,是4月9日,正好过去了一个月,终于排到了。

现在pretext在社区的讨论没有前段时间那么火了。

做前端开发这么多年,有一个心心念念的布局效果一直想要实现,那就是完全文字环绕效果。

CSS shapes布局可以实现一侧环绕,但是想要完整环绕,一直没有什么好的解决方案。

pretext的出现让我意识到有了比较好的解决方法了。

Pretext 是一个纯 JavaScript/TypeScript 库,用于多行文本测量和布局。它快速、准确,支持各种语言。

Pretext 避免了 DOM 测量的需求,实现了自己的文本测量逻辑。

这个库由 Cheng Lou 创建,通过 Canvas 测量文本宽度,然后使用纯数学运算进行布局。这样可以避免触发浏览器的重排操作,从而获得极高的性能。

使用 Pretext,你可以实现许多有趣的效果,比如文字环绕浮动元素、动态布局调整、以及各种创意排版效果。文本可以流畅地环绕图片或其他形状,创造出精美的视觉效果。

项目地址:https://github.com/chenglou/pretext

目前Star数已经是惊人的46.4K!

pretext CSS数

二、百炼成钢,案例演示

以下演示都是使用AI生成的,尼玛AI真的是越来越强了。

1. 矩形环绕

您可以狠狠地点击这里:pretext实现文字居中环绕图片demo

拖动图片,可以看到文字实时环绕图片的效果。

由于截屏录制的GIF体积有些大,我直接截个静态图给大家看看吧。

文字图片环绕示意

2. 非透明像素环绕

下面,我们将上述效果升级下,文字不再是环绕图片区域,而是环绕图片中的非透明图像轮廓,也是可以实现的。

您可以狠狠地点击这里:pretext实现文字居中环绕图片非透明区域demo

效果示意参见下面的截图:

透明区域文字环绕

可以看到,文字不再是围绕图片所占据的矩形区域环绕排版,而是围绕PNG图片的非透明像素部分进行环绕。


完整的实现代码demo页面上可见。

三、心愿已了,五一总结

本文不谈语法,不谈具体细节,因为没必要。

要是几年前,我估计要花半天时间学习API,然后调试demo。

如今,AI,还不是最好的AI模型,已经可以巴拉巴拉把我的需求一口气实现,自己只需要再调整一些细节即可。

还学啥,有啥好学的,只需要知道有这么个东西就好了。

不管怎么说,心心念念的排版效果如今得见天颜,也算是了了一桩心愿。

最后,总结下我的五一节。

很简单,钓了5天的鱼,😄😄😄

给老板送了几年的温暖,现在钓鱼手艺越来越好了。

最近钓货一栏

依次是:

218五小时,太仓晓渔园。128五小时,张家港林姐垂钓。常熟小飞鱼塘,99五小时。崇明三农,158三小时。上海东翔,138六小时。上海星河,180六小时。

美少女比耶

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

(本篇完)

JSON.rawJSON方法的作用是什么?

2026-05-06 16:49:48

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

在前端开发中,JSON对象的方法我们最常用的就是parsestringify,几乎每天都在和它们打交道——接口请求后用parse解析数据,提交数据前用stringify序列化对象。

但如果你最近关注JS的新特性,会发现多了一个不怎么起眼但却很实用的方法:JSON.rawJSON。

很多同学看到这个方法会一脸疑惑:stringify已经能序列化数据了,为什么还要整个rawJSON?它到底解决了什么问题?

今天我们就抛开晦涩的官方文档,用最接地气的方式,把JSON.rawJSON的作用、用法和避坑点讲明白,看完就能直接用到项目里。

JSON示意图

一、stringify的“隐形坑”

在讲rawJSON之前,我们先看一个日常开发中很容易踩的坑,也是rawJSON诞生的核心原因。

大家都知道,JavaScript里的数字都是浮点数,这就导致有些超大整数无法被精确表示。比如我们有一个超大的ID值:12345678901234567890,当我们用stringify序列化它时,会发现一个诡异的问题:

// 期望序列化出 12345678901234567890
console.log(JSON.stringify({ id: 12345678901234567890 }));
// 实际输出:{"id":12345678901234567000}

注意看,末尾的数字被自动“四舍五入”了,精度直接丢失!这不是bug,而是JavaScript浮点数的固有缺陷——它无法精确表示所有整数,当数字超过2^53时,就会出现精度偏差。

这个问题在对接后端接口时尤为常见,比如后端返回的用户ID、订单号是超大整数,前端用stringify序列化后,再传给后端,就会导致ID错误,进而出现查不到数据、订单异常等问题。

有人会说:那我把数字转成字符串不就行了?比如这样:

console.log(JSON.stringify({ id: "12345678901234567890" }));
// 输出:{"id":"12345678901234567890"}

这样确实能保留精度,但新的问题来了:后端接口期望的id是数字类型,而我们传过去的是字符串,会导致接口报错。这就陷入了一个两难境地:传数字,精度丢失;传字符串,类型不符。

而,JSON.rawJSON 就是为了解决这个“两难”而生的。

解决两难

二、JSON.rawJSON的核心作用

官方对JSON.rawJSON的定义很简单:创建一个“原始JSON对象”,当这个对象被JSON.stringify序列化时,会直接将传入的字符串作为JSON文本的一部分,不做任何转义、不做任何修改,也不会添加额外的引号。

说得更通俗一点:rawJSON就像是一个“容器”,你把一段合法的JSON文本放进去,它会告诉stringify:“这段内容你别乱动,直接原样输出就行”。

我们用刚才的超大整数案例来测试一下,就能瞬间明白它的作用:

// 传入一段表示超大整数的JSON文本(注意是字符串形式)
const rawId = JSON.rawJSON("12345678901234567890");
// 序列化包含rawJSON对象的对象
console.log(JSON.stringify({ id: rawId }));
// 输出:{"id":12345678901234567890}

完美!

完美表情包

既保留了超大整数的精度,又保证了序列化后的id是数字类型,后端接口能正常接收。

这就是rawJSON最核心的作用——让我们能“手动控制”JSON序列化的结果,解决stringify无法处理的精度丢失、类型不符等问题。

再举一个更直观的对比,帮大家分清rawJSON和普通字符串的区别:

// 普通字符串
const normalStr = "12345678901234567890";
// rawJSON对象
const rawStr = JSON.rawJSON("12345678901234567890");

// 分别序列化
console.log(JSON.stringify({ normal: normalStr })); // {"normal":"12345678901234567890"}
console.log(JSON.stringify({ raw: rawStr }));       // {"raw":12345678901234567890}

很明显:普通字符串会被stringify加上引号,变成字符串类型;而rawJSON对象会被原样输出,保持原本的JSON类型(这里是数字)。

三、rawJSON的3个关键细节

很多同学用rawJSON时会踩坑,不是因为方法本身难,而是没注意它的几个关键细节。

结合日常开发场景,我把这些细节整理出来,帮大家避坑。

避坑

细节1:传入的必须是“合法的JSON原语文本”

rawJSON的参数必须是一段合法的JSON文本,而且只能是JSON原语(数字、字符串、布尔值、null),不能是对象或数组。

如果传入的文本不合法,或者是对象/数组,会直接抛出SyntaxError错误。

// 合法:数字文本
JSON.rawJSON("123"); // 正确
// 合法:字符串文本(注意内部要加引号)
JSON.rawJSON('"hello"'); // 正确,序列化后是"hello"
// 合法:布尔值文本
JSON.rawJSON("true"); // 正确
// 非法:传入对象文本(会报错)
JSON.rawJSON('{"name":"张三"}'); // SyntaxError
// 非法:传入不合法的JSON文本(会报错)
JSON.rawJSON("123abc"); // SyntaxError

这里有个容易踩的坑:如果想序列化字符串类型的内容,传入的文本必须加上引号。

比如我们想让序列化后的结果是字符串”hello”,就必须传入'”hello”‘(外层是参数的引号,内层是JSON字符串的引号),否则会报错。

细节2:返回的是“特殊对象”,不是普通值

JSON.rawJSON返回的不是字符串,也不是数字,而是一个特殊的“原始JSON对象”。这个对象有两个特点:

  1. 它的原型是null,而且是冻结的(frozen),无法修改它的属性,也无法给它添加新属性,避免被意外篡改;
  2. 它有一个public属性rawJSON,里面存储着我们传入的原始字符串;

我们可以打印出来看看:

const raw = JSON.rawJSON("123456");
console.log(raw); // 输出:{ rawJSON: "123456" }
console.log(Object.getPrototypeOf(raw)); // null
console.log(Object.isFrozen(raw)); // true

这个特殊对象的唯一作用,就是告诉JSON.stringify:“我里面的内容要原样输出”。

除此之外,它没有其他实际用途,也不能当作普通对象使用。

细节3:配合JSON.isRawJSON判断是否为rawJSON对象

既然rawJSON返回的是特殊对象,那我们怎么判断一个值是不是rawJSON对象呢?

JS提供了对应的判断方法:JSON.isRawJSON。

这个方法很简单,传入一个值,返回布尔值,true表示是rawJSON对象,false表示不是。

const raw = JSON.rawJSON("123");
const normal = 123;

console.log(JSON.isRawJSON(raw)); // true
console.log(JSON.isRawJSON(normal)); // false
console.log(JSON.isRawJSON({})); // false

这个方法在实际开发中很实用,比如我们接收一个未知值,想判断它是不是rawJSON对象,再决定如何处理,就可以用它。

四、JSON.rawJSON的实际应用场景

讲完了细节,我们再说说rawJSON在实际开发中到底能用到哪些地方。除了前面提到的“超大整数精度保留”,还有两个高频场景。

场景1:解决超大整数、BigInt序列化问题

除了普通的超大整数,BigInt类型的序列化也是一个痛点。默认情况下,stringify无法序列化BigInt,会直接报错:

const bigNum = 12345678901234567890n;
JSON.stringify({ num: bigNum }); // Uncaught TypeError: Do not know how to serialize a BigInt

这时候,我们就可以用rawJSON来解决:
const bigNum = 12345678901234567890n;
// 将BigInt转成字符串,传入rawJSON
const rawBig = JSON.rawJSON(bigNum.toString());
// 序列化后正常输出数字类型
console.log(JSON.stringify({ num: rawBig })); // {"num":12345678901234567890}

这样既解决了BigInt无法序列化的问题,又保留了数字类型,后端接口也能正常接收。

场景2:精确控制JSON序列化格式

有时候,我们需要让JSON序列化的结果完全符合特定格式,而stringify的默认处理会不符合预期。

例如下面的例子:

// 普通方式 - JS 自动解析 Unicode 转义
const emoji1 = "\ud83d\ude04";  // JS 字符串 = 1个字符 "😄"
console.log(JSON.stringify({ emoji: emoji1 }));
// 输出: {"emoji":"😄"}  ❌ 自动转成了表情符号

// rawJSON 方式 - 强制保留转义序列形式
const emoji2 = JSON.rawJSON('"\\ud83d\\ude04"');
// 参数 "\\\\u..." 在 JS 中 = "\\ud83d\\ude04"(2个反斜杠)
// 作为 JSON 字符串 = \ud83d\ude04(1个反斜杠)
// 解析后 = "😄"
console.log(JSON.stringify({ emoji: emoji2 }));
// 输出: {"emoji":"\ud83d\ude04"}  ✅ 保留转义序列字面形式

不过这种场景实际开发中并不多见。

避坑提醒:这些情况别用rawJSON

rawJSON虽然实用,但不是万能的,有些场景下使用反而会给自己添麻烦。结合我的开发经验,总结了3个“别用”的场景:

  1. 不需要精确控制序列化结果时,别用。如果只是普通的对象序列化,用stringify就足够了,rawJSON会增加不必要的代码复杂度;
  2. 传入的内容不是合法JSON原语时,别用。强行传入对象、数组或非法JSON文本,会直接报错,反而影响代码运行;
  3. 兼容性要求高的项目,慎用。rawJSON是ES2025引入的新特性,目前(2026年)Chrome、Edge等现代浏览器已经支持,但一些老旧浏览器(比如IE)、部分国产浏览器(尤其是电视端、低端设备)还不支持,需要做好兼容处理(可以用ponyfill填充)。

五、兼容性、ponyfill及结语

rawJSON方法的兼容性如下图所示:

rawJSON兼容性

ponyfill

JSON.rawJSON 和 isRawJSON 的 ponyfill可以参见这个项目:https://github.com/ungap/raw-json

最后总结

其实JSON.rawJSON的核心逻辑很简单,一句话就能概括:让JSON.stringify“原样输出”一段合法的JSON原语文本,解决精度丢失、类型不符、格式控制等问题。

我们不用把它想得太复杂,记住它的核心场景——超大整数/BigInt序列化、精确控制JSON格式,在需要的时候用它,就能解决很多开发中的“隐形坑”。

另外,提醒大家一句:rawJSON不是要替代stringify,而是对stringify的补充。大多数情况下,我们还是用stringify;只有遇到stringify解决不了的问题时,再考虑用rawJSON。

最后留一个小练习,大家可以自己动手试试:用rawJSON序列化一个布尔值true,再用stringify输出,看看结果是什么?欢迎在评论区留下你的答案~

美少女飞吻

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

(本篇完)

如何使用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

(本篇完)