2025-01-22 23:22:42
by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=11509
本文可全文转载,独立域名个人网站无需授权,但需要保留原作者、出处以及文中链接,任何网站均可摘要聚合,商用请联系授权。
目前,Web浏览器提供了原生的Object对象深度克隆方法structuredClone()
函数。
使用方法很简单,JS代码如下所示:
// 创建一个具有值和循环引用的对象 const original = { name: "zhangxinxu" }; original.itself = original; // 克隆 const clone = structuredClone(original); // 两者对象是不相等的 console.assert(clone !== original); // 两者的值是相等的 console.assert(clone.name === "zhangxinxu"); // 并且保留了循环引用 console.assert(clone.itself === clone);
语法如下:
structuredClone(value, options)
其中:
transfer
的参数值,其值为一组可转移的对象,它们将被移动而不是克隆到返回的对象中。关于transfer
可选参数transfer
多用在一些大数据传输中(转移相比克隆可以节约内存开销),这里有个案例供大家参考:
const original = new Uint8Array(1024); const clone = structuredClone(original); console.log(original.byteLength); // 1024 console.log(clone.byteLength); // 1024 original[0] = 1; console.log(clone[0]); // 0 // 转移Uint8Array会引发异常,因为它不是可转移对象 // const transferred = structuredClone(original, {transfer: [original]}); // 我们可以转移Uint8Array.buffer const transferred = structuredClone(original, { transfer: [original.buffer] }); console.log(transferred.byteLength); // 1024 console.log(transferred[0]); // 1 // Uint8Array.buffer转移后就无法使用了 console.log(original.byteLength); // 0
window全局的structuredClone()
方法兼容性还是不错的,目前所有常见浏览器都已经支持了,如下截图所示:
考虑到总会有一些用户手机舍不得或者忘记或者懒得升级,面对偏外部用户的产品,建议还是同时引入Polyfill。
JS不同于CSS,要是JS某个方法不支持,然后你去运行他,很可能会导致整个页面白屏,这是Vue和React项目中是常有的事情(也包括各类小程序)。
所以,我们需要引入Polyfill,可以试试这个项目:https://github.com/ungap/structured-clone
使用示意:
import structuredClone from '@ungap/structured-clone'; const cloned = structuredClone({any: 'serializable'});
还是很easy的啦。
其实,上面的Polyfill还有不少其他的功能,就等大家自行去探索啦。
之前我深度拷贝一个Object对象会使用JSON.parse(JSON.stringify(obj))
来实现,虽然可以满足绝大多数的场景,但有时候会出问题。
例如,当对象的属性值是Date()
对象的时候,案例示意:
const originObj = { name: "zhangxinxu", date: new Date() }; const cloneObj = JSON.parse(JSON.stringify(originObj)); // 结果是 'object' console.log(typeof originObj.date); // 结果是 'string' console.log(typeof cloneObj.date);
可以看到,本应实时显示当下时间的属性值变成了固定死的字符串值(也可以看截图运行结果),这并不是我们希望看到的。
而浏览器提供的structuredClone()
方法则没有这个问题,使用示意:
const originObj = { name: "zhangxinxu", date: new Date() }; const cloneObj = structuredClone(originObj); // 结果是 'object' console.log(typeof originObj.date); // 结果是 'object' console.log(typeof cloneObj.date);
当然,还包括很多其他类型的对象也是如此,包括:Date, Set, Map, Error, RegExp, ArrayBuffer, Blob, File, ImageData等。
如果需要复制的对象层级简单,那么我们使用点点点,或者Object.assign()
、Object.create()
方法是没问题的,例如:
const originObj = { name: "zhangxinxu" }; // ok没问题 const cloneObj = { ... originObj } // ok没问题 const cloneObj = Object.assign({}, originObj) // ok没问题 const cloneObj = Object.create(originObj)
可如果对象的属性值也是个对象,那么上面的方法就有问题,例如:
const originObj = { name: "zhangxinxu", books: ['CSS世界'] }; // ok没问题 const cloneObj = { ... originObj } cloneObj.books.push('HTML并不简单'); // 结果原对象的books也一起变化了 console.log(originObj.books);
控制台运行结果不会骗人:
当然,structuredClone
方法也不是万能的,例如DOM对象是不能参与复制的。
// 会报错 structuredClone({ el: document.body })
函数也不能复制:
// 会报错 structuredClone({ fn: () => { } })
标题这些类型的东西也不会被深度复制,比方说像getter,克隆的会是其值,而不是getter函数本身。
structuredClone({ get foo() { return 'bar' } }) // 结果: { foo: 'bar' }
原型链也是不会被复制的,因此,如果克隆MyClass的实例,克隆的对象将不再是该类的实例(但该类的所有有效属性都将被克隆)
class MyClass { foo = 'bar' myMethod() { /* ... */ } } const myClass = new MyClass() const cloned = structuredClone(myClass) // 结果 { foo: 'bar' } cloned instanceof myClass // false
好,本文的内容就这些,应该是春节前的最后一篇文章了,本来以为内容不多,但写着写着,发现里面可讲的东西还不少。
我明天就请假回老家了,算算,可以连休12天,还真是富裕的假期。
就是过年很多鱼塘不开门,想要出去钓鱼,还有些困难。
唉,再说吧。
话不多说,祝大家蛇年快乐,万事如意。
在家要是无聊,可以看看技术书籍
本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=11509
(本篇完)
2025-01-14 23:10:19
by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=11545
本文可全文转载,独立域名个人网站无需授权,但需要保留原作者、出处以及文中链接,任何网站均可摘要聚合,商用请联系授权。
SVG的曲线绘制在10多年前,我其实有撰文介绍过,也算是当时的热门文章了,见:“深度掌握SVG路径path的贝塞尔曲线指令”。
其中就有提到圆弧指令的绘制语法,不过这篇文章主要是讲贝塞尔曲线的,因此,圆弧指令并未多做介绍。
然后,最近遇到了一个需求,如下图所示,实现下图所示的渲染效果:
由于其中有个动态尺寸的圆环,用来表示走过的百分比进度,因此,这里使用静态的图片是不合适的,而那个动态圆环本身就是需要使用SVG实现的(一是因为端点是圆角,二是因为有路径动画),既然都A了,那B岂不就可以顺便也实现下。
因此,最终决定,总计4个圆环全部都手搓。
而手搓SVG圆环的指令是M…A…组合,具体语法如下:
圆弧指令其实挺简单的。
如下所示:
M x1 y1 A rx ry x-axis-rotation large-arc-flag sweep-flag x2 y2
其中:
再然后:
x-axis-rotation
是x轴旋转值,这个值平常我们用不到,一般都是0large-arc-flag
表示是否是大弧。只这样的,再一个圆形上随便取两个点,是不是会有两段弧线?如下图所示:
large-arc-flag
的值为0表示使用的是小弧(上图红色实线),为1则是大弧(蓝色虚线)。
sweep-flag
表示是否是顺时针,值为1则是顺时针,为0则表示逆时针。而效果图里面的每段弧线都不超过1/2个圆,因此都是小圆弧,故而,我们需要确定的,其实就是起点和终点而已。
假设SVG的半径是40,则圆弧路径就是:
M x1 y1 A 40 40 0 0 1 x2 y2
具体转换可参见下面的GIF图片:
因此,弧线的path路径指令看似复杂,实际在开发过程中,只需要确定起点和终点坐标就可以了。
坐标的计算就要用到高中知识了,高中还是初中的?记不清了,什么时候学的不重要,反正就是正弦余弦计算。
我还在小本本上画了画:
然后就得到了如下所示的坐标计算方法:
/* angle: 数值,逆时针旋转的角度 */ const pathTogether = function (angle) { const r = 40; // 总角度 const totalAngle = 360; // x y 是圆弧的点坐标 let x = 0; let y = 0; // 坐标计算 if (angle只要给定角度大小(逆时针角度),就能返回对应的准确坐标点的。
然后就是套入就可以了,最后得到了如下的SVG路径:
<svg width="100" height="100" viewBox="0 0 100 100"> <path d="M58.32 10.87A40 40 0 0 1 83.16 27.63" fill="none" stroke="#CDB9FF" stroke-width="16" stroke-linecap="round"></path> <path d="M89.39 43.05A40 40 0 0 1 35.02 87.09" fill="none" stroke="#B293FF" stroke-width="16" stroke-linecap="round"></path> <path d="M21.23 77.79A40 40 0 0 1 41.68 10.87" fill="none" stroke="#7D49FF" stroke-width="16" stroke-linecap="round"></path> </svg>实时渲染效果如下所示(非正版文章会看不到):
是不是发现还挺简单的。
四、文字环绕效果
至于图中的文字环绕效果,这个SVG自带此能力,使用
<textPath>
元素就可以了,将其href属性值锚定制定的路径元素即可。这个之前已经撰文介绍过了,参见:“文字沿着不规则路径排版布局的实现”
回到本例,使用代码如下所示:
<svg width="100" height="100" viewBox="0 0 100 100"> <path id="pathCurrent" d="M21.23 77.79A44 44 0 0 1 41.68 10.87" fill="none" stroke="red"></path> <text font-size="9"> <textPath href="#pathCurrent"> 我们一起走过的日子 </textPath> </text> </svg>SVG真实代码的解析结果就是下面这图这样的(实际开发,红色路径线条是不显示出来的):
至于2022~2024文字的环绕对齐,也是类似的,就是需要一些偏移,可以使用
transform
属性,例如:<text font-size="7" x="0" fill="#2B0095" transform="translate(-3, 2)" stroke="#CDB9FF" stroke-width="2" paint-order="stroke"> <textPath href="#path2022"> 2022 </textPath> </text>完整的效果,如下图所示:
五、结束语
一点工作心得总结,以后遇到类似的需求,知道去哪里找,开发效率就高,顺便帮助遇到类似需求的小伙伴。
下面是闲聊扯淡时间。
小朋友的期末考试成绩出来了,怪怪,都已经四年级了。
本来指望着能够进入前三十就好了。
结果不知道怎么回事,奇了怪了,这次期末考试,按分数排名第6,总排名第16名,居然一下子进入前20了。
数学成绩上了4年,头一次考到A+。
难以理解,我的心情就像下图这样。
我靠,要是稳定这个水平,我要考虑初中学区房了。
💸💷💶💵💴💰
本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=11545(本篇完)
2025-01-12 23:53:31
by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=11507
本文可全文转载,独立域名个人网站无需授权,但需要保留原作者、出处以及文中链接,任何网站均可摘要聚合,商用请联系授权。
在12年的时候,乖乖,12年啊,都十几年前了,我有写过关于postMessage的跨页面通信的文章,见这里。
结果这么多年下来,在生产环境使用上述postMessage通信的机会屈指可数,这种跨页面通信平常的交互式页面是鲜有机会接触的。
然后,现在有多了个Broadcast Channel API,也是实现页面见通信的。
和十多年前的这个通信区别在于,这个是广播式的,而非点对点。
所谓广播通信,就是所有域名相同的页面,A发送消息,其他所有页面都能接收到,这在需要数据实时同步的场合非常有用。
Broadcast Channel API看起来复杂,实际使用简单地出乎意料,我想,最复杂的应该就是这个API的拼写了。
是这样的,所有需要通信的页面都new一个相同的广播通道,就像这里:
const bc = new BroadcastChannel('zhangxinxu');
然后一个页面发送,就像这样:
bc.postMessage('欢迎支持正版书籍');
然后其他页面就能接收到这个信息:
const bc = new BroadcastChannel('zhangxinxu'); bc.onmessage = function (event) { console.log(event.data); // 输出'欢迎支持正版书籍' };
就这样结束了。
为了演示方便,我使用iframe将数个页面击中在了一起,左侧是主页面,右侧分别是iframe-a.html和iframe-b.html。
这三个页面都创建了一个同名的BroadcastChannel对象。
主页面发送,两个iframe内嵌页接受,结果,当我们点击按钮的时候,如下图所示:
右侧的iframe页面就接受到了发送的信息,如下截图所示:
我们还可以修改输入框的内容,查看发送不同信息的结果,例如,我们厂最近热播的《大奉打更人》和《国色芳华》
您可以狠狠地点击这里:Broadcast Channel API使用示意demo
目前广播通信API的兼容性还是很不错的,caniuse网站截图:
BroadcastChannel不仅可以用在window上下文环境中,在Web Worker中也是可以自如使用的。
不仅可以是iframe间广播通信,各个浏览器选项卡之间通信也是可以的,这里我就不重复演示了,肯定是支持的。
通道名字可以使用name属性值获取,例如:
console.log(bc.name); // 输出结果是:zhangxinxu
如果想要关闭通道,可以使用close()
方法。
bc.close();
通道一旦关闭,你再使用postMessage()
发送数据就会报错,或者使用message
事件接收数据也不会成功,反而会触发messageerror
事件。
在上面的demo中,通信讯息的接受用的是onmessage
方法,实际上,使用addEventListener去绑定事件也是可以的,例如:
bc.addEventListener("message", (event) => { console.log(event.data); })
说啥结语,想不到啥,小朋友放寒假了,时间过得好快,要过年了,明年40了。
哦,对了,年终总结忘记写了。
年前抽时间写一下吧,好好想想怎么写。
行吧,就这些吧,表达欲不是年轻的时候了。
感谢阅读,尤其这么多年还在关注我的小伙伴们。
如果可以,希望可以顺手下,比心。
本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=11507
(本篇完)
2025-01-02 23:46:13
by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=11505
本文可全文转载,独立域名个人网站无需授权,但需要保留原作者、出处以及文中链接,任何网站均可摘要聚合,商用请联系授权。
三年前,我有介绍过渐弱动画的媒体查询prefers-reduced-motion
,参见文章“你不知道的CSS media查询与用户体验”的第5小节。
使用示意:
@media (prefers-reduced-motion: reduce) { /* ⽤户希望减弱动画 */ }
目前Chrome浏览器新支持了一个名为prefers-reduced-transparency
的媒体查询,其设计初衷和使用背景与上面提到的prefers-reduced-motion
类似。
是这样的:
此时,用户就会考虑关闭操作系统的半透明设置选项。
目前,Windows、macOS 和 iOS 都提供了系统偏好设置,可降低或移除界面的透明度。
例如,在我的Windows 10操作系统下,在显示设置里,就有这么个开关选项,如下截图所示:
而prefers-reduced-transparency
的媒体查询就是为这种场景设计的。
使用示意:
.overlay { --opacity: .5; background: rgba(0 0 0 / var(--opacity)); @media (prefers-reduced-transparency: reduce) { --opacity: .95; } }
不过实际开发并不需要我们去系统层面进行自定义设置,Chrome的开发者工具就提供了相关的选项。
操作路径如下截图所示,首先,开启Rendering选项卡面板,如果没有,参照下图示意打开:
其中就有prefers-reduced-transparency:reduce
的下拉选项,截图如下:
此时,我们就可以针对性地进行用户体验方面的处理的。
我们通过一个简单的案例,开看一下prefers-reduced-transparency:reduce
的执行效果。
点击按钮,显示模态对话框(浏览器原生的能力),然后通过CSS代码以及媒体查询,控制模态背景的半透明度,相关的HTML和CSS代码如下所示:
<button onClick="dialog.showModal();" >点击我</button> <dialog id="dialog" onclick="this.close()" >我是模态框,看看背景的半透明是多少?</dialog>
dialog::backdrop { --opacity: .5; background: rgba(0 0 0 / var(--opacity)); @media (prefers-reduced-transparency: reduce) { --opacity: .95; } }
在默认情况下,我们点击按钮,模态框的背景半透明度,大致是下图这般模样:
黑色半透明背景后面的文字和图形可谓是清晰可见。
当我们,Chrome控制台Rendering选项卡开启prefers-reduced-transparency:reduce
选项后,会发现,背景一下子变得好黑,比包拯还黑。
这种渲染对于有视力障碍的人来说,就会舒服很多。
眼见为实,您可以狠狠地点击这里:CSS prefers-reduced-transparency与模态半透明颜色demo
来看下此@media媒体查询语句的兼容性,如下图所示,可以看到Chrome 118开始支持,而Firefox(实验支持)和Safari还是红灿灿的一片。
所以,目前,上面的demo演示页面只能在Chrome,或者版本比较高的Android手机下查看才有效果。
手机查看的话,需要去手机的设置里,设置减少透明度这个选项。
好吧,就这些,一个与用户体验密切相关的CSS新特性。
不过,按照以往的尿性开看,没几个前端会在真实项目中使用的,投入产出比太低,吃力不讨好。
所以,大家了解下就好了,万一以后有机会开发个很多人使用的UI组件,这个就有用处了,毕竟用的人多,价值就上来了。
感谢阅读,欢迎。
😋😛😝😜
本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=11505
(本篇完)
2024-12-29 22:25:59
by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=11503
本文可全文转载,独立域名个人网站无需授权,但需要保留原作者、出处以及文中链接,任何网站均可摘要聚合,商用请联系授权。
在过去,<select>
下拉元素的子元素,只能是<option>
和<optgroup>
(下拉选项分组)元素。
而现在,<select>
又多支持了一个元素,为<hr>
元素。
可以在下拉选项中创建分隔线。
例如:
<select is="ui-select"> <option>选项1.1</option> <option>选项1.2</option> <hr> <option>选项2.1</option> <option>选项2.2</option> </select>
实时渲染效果如下:
下图是我电脑上的截图效果:
我试试看这个hr样式能不能自定义。
结果,和option元素一样,是无法自定义的,sad 😔。
首先,hr元素可以不闭合。
其次,如果<select>
元素需要设置<hr>
分隔效果,那么祖先元素不能是<p>
元素,因为可能会有渲染问题。
例如下面这样的HTML代码:
<p> <select is="ui-select"> <option>选项1.1</option> <option>选项1.2</option> <hr> <option>选项2.1</option> <option>选项2.2</option> </select> </p>
在Chrome 129下,<hr>
元素会中断<select>
元素,变成下图所示的效果:
不过Chrome在版本131之后修复了这个渲染bug,不过用户不一定实时升级Chrome浏览器,因此,还是避免p > hr
的标签组合。
最先开始支持的是Chrome浏览器,时间应该是去年年底,caniuse上的截图:
Safari支持最晚,今年3月份才开始支持。
为什么都是棕黄色,而非绿色呢,是因为可访问性这块还没怎么支持。
LuLu UI组件的设计理念是基于原生HTML构建,既然浏览器原生支持了下拉框中的hr元素,LuLu UI 也定然要跟随支持。
所以,昨天在家,抽空弄了下,已经发版了。
演示页面地址访问:https://l-ui.com/edge/apis.select.html
Nice!
15年前刚写博客那会儿,恨不得每篇文章万把字,现在,年纪上去了,更新也还更新,但是字数缩写得厉害。
精力没有以前那么旺盛了,创作欲也下来了。
瞧瞧本文,之前篇幅的1/10.
不过嘛,现在的年轻人都喜欢短平快,说不定这种短短的内容他们更喜欢。
好了,不唠叨了。
这周三元旦,吼吼,天气也不错,可以出远门钓个鱼。
桀桀桀。
本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=11503
(本篇完)
2024-12-23 00:31:51
by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=11498
本文可全文转载,独立域名个人网站无需授权,但需要保留原作者、出处以及文中链接,任何网站均可摘要聚合,商用请联系授权。
目前,浏览器是有能力获取系统的一些信息的,包括内存大小,CPU/GPU、电量等。
很简单,下面一行代码即可:
navigator.deviceMemory
例如在我的Chrome浏览器控制台的输出结果就是:
deviceMemory
的兼容性如下截图所示,目前仅Chrome浏览器支持。
目前Web没法获取CPU具体的参数信息,但是可以获取CPU的核数。
同样是navigator对象,不过属性值看不到CPU三个字母的影子,名为hardwareConcurrency,表示可以并行运行的硬件数量。
代码示意:
navigator.hardwareConcurrency
运行结果示意:
没想到,我的破电脑CPU是12核的,应该与去年换主板有关,不过没看出性能有多少提升。
不过Mac下貌似可以看出芯片的品牌,例如我的Mac办公电脑运行navigator.platform
返回的是'MacIntel'
。
hardwareConcurrency
值的兼容性比deviceMemory
好多了,参见下图,可以看到所有标准浏览器都是支持的:
也是一行语法:
navigator.gpu
不过navigator.gpu
返回的并不是具体的数值,而是GPU对象,这个对象和WebGPU API关联密切。
支持一个属性和两个方法,属性是wgslLanguageFeatures
,方法为requestAdapter()
和getPreferredCanvasFormat()
。
而navigator.gpu.wgslLanguageFeatures.size
返回的是当前Web环境支持的WGSL语言特征数量,例如我的电脑windows电脑返回的是4,Mac笔记本也是4,有些GPU比较老,数量就不会是4.
不过这个API……怎么说呢,跟想象的有些不一样,检测的其实是浏览器是否支持WebGPU,以及是否可以调用适配器,以使用WebGL等功能。
我看来下兼容性,嘿,最新的Safari和Firefox都已经支持了,还挺巧的。
电量获取使用getBattery()方法,使用示意:
let batteryIsCharging = false; navigator.getBattery().then((battery) => { // 当前电量水平是...范围0-1 console.log(battery.level); // 是否在充电 batteryIsCharging = battery.charging; // 充电状态变化的时候 battery.addEventListener("chargingchange", () => { batteryIsCharging = battery.charging; }); });
通过上述代码,我们就可以获悉当前设备的电量状态,以及是否是充电状态。
像是PC电脑,接电源的,没电池的,只要设备运行,电量范围就一定是1,但是移动笔记本就会不一样。
我们可以据此,显示自定义的电池进度,充电动画,或者低电量模式的交互提醒(例如,关闭不必要的动画以帮助用户省电)等。
这个API其实和用户体验还是走得蛮近的,兼容性的话,我看看,是这样的:
有些遗憾,仅仅Chrome浏览器支持,怪不得社区很少见到有对此API的讨论。
而且从Chrome开始支持的时间来看,短时间内,看不到Safari浏览器支持的迹象。
这么好的API,目前也只能当做玩具使用了。
navigator
对象还提供了很多其他的硬件识别,例如蓝牙,虚拟键盘,USB,外置摄像头,是否掉线(navigator.onLine)等。
篇幅原因,我就不一一展示了,有兴趣可以访问对应的文档。
只要遇到类似的场景,知道Web有这样的能力就可以了。
ok,就这样吧,轻轻松松,又水了一篇文章,佛佛佛。
本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=11498
(本篇完)