2024-09-19 08:00:00
对于SPA,将更新DOM的方法updateDomTrigger
作为参数传递给document.startViewTransition(updateDomTrigger)
方法,浏览器会<u>先截取</u>当前页面DOM元素的快照(声明了 view-transition-name
CSS属性的DOM元素,默认是:root),<u>再执行</u>updateDomTrigger
方法,然后<u>再执行过渡动效</u>。
对于MPA,添加以下CSS规则,过渡效果会在导航到下一个同源页面的时候自动触发。
@view-transition {
navigaion: auto;
}
旧视图快照opacity
从1 到 0,新视图快照opacity
从0 到 1。
HTML会针对视图动效生成以下伪元素树:
::view-transition
└─ ::view-transition-group(root)
└─ ::view-transition-image-pair(root)
├─ ::view-transition-old(root)
└─ ::view-transition-new(root)
view-transition-name
CSS属性,对不同的元素使用不同的自定义动效。因此,每一个 view-transition-name
都对应一个view-transition-group
(默认是root
)view-transition-old
指向动效前元素的静态快照view-transition-new
指向动效后元素的实时快照具体的动画效果由CSS animation设置:
/* 只需要添加以下@view-transition规则,就会在切换页面时,触发默认的“淡化”动效 */
@view-transition {
navigation: auto;
}
/* 自定义默认的动画行为 */
::view-transition-group(root) {
animation-duration: 0.5s;
}
/* 创建自定义动画 */
@keyframes move-out {
from {
transform: translateY(0%);
}
to {
transform: translateY(-100%);
}
}
@keyframes move-in {
from {
transform: translateY(100%);
}
to {
transform: translateY(0%);
}
}
/* 将自定义动画应用到新旧元素 */
::view-transition-old(root) {
animation: 0.4s ease-in both move-out;
}
::view-transition-new(root) {
animation: 0.4s ease-in both move-in;
}
对于SPA,document.startViewTransition()
方法会返回一个 ViewTransition
对象实例,这个实例包含多个 promise:
ViewTransition.ready
在创建伪元素树且动画即将开始时执行。ViewTransition.finished
在动画完成后、且新的页面视图对用户可见且具有交互性时执行。对于MPA:
pageswap
事件,事件的event对象上的PageSwapEvent.viewTransition
属性包含了 ViewTransition
实例, PageSwapEvent.activation
包含当前切换页面的导航类型、当前文档和目标文档历史记录。pagereveal
事件,事件的event对象上的PageSwapEvent.viewTransition
属性包含了 ViewTransition
实例。pagereveal
事件的script脚本在渲染动效之前执行,需要给脚本添加 blocking=“render”
属性;<link rel="expect" href="#lead-content" blocking="render" />
,其中#lead-content
指向对应元素。2024-09-04 08:00:00
通过z-index来控制层级是很常见的前端需求,
但是你是否遇到过,无论z-index设置多大,仍然无法将一个元素移到最上层的情况?
这是因为z-index依赖于一个抽象的概念:Stacking Context
stacking context是层叠上下文,决定了DOM元素上下层级关系
每个层叠上下文都由一个元素创建,这个元素被称为stacking context的根元素
z-index
不是默认值auto
的元素(z-index=0也会创建层叠上下文)isolation: isolate;
:创建一个新的堆叠上下文,隔离混合效果DOM结构:
div1
div2
div3
-> div4
-> div5
-> div6
最终的层级顺序:
这个扩展在浏览器devtools中列出了网页上的stacking context。在遇到z-index和层级问题时,可以让我们更快的定位问题。
2024-09-02 08:00:00
空值合并运算符(??),识别null和undefined值;MDN
const foo = null ?? 'default string';
console.log(foo);
// Expected output: "default string"
const baz = 0 ?? 42;
console.log(baz);
// Expected output: 0
动态引入,import函数支持异步引入js模块;MDN
import(moduleName)
import(moduleName, options)
await import('/modules/my-module.js')
类私有属性,以#
为前缀,无法在类的外部引用;MDN
class ClassWithPrivateField {
#privateField
}
逻辑与赋值&&=
,逻辑或赋值||=
;MDN
const a = { duration: 50, title: '' };
a.duration ||= 10;
console.log(a.duration);
// expected output: 50
a.title ||= 'title is empty.';
console.log(a.title);
// expected output: "title is empty"
let a = 1;
let b = 0;
a &&= 2;
console.log(a);
// Expected output: 2
b &&= 2;
console.log(b);
// Expected output: 0
#!
HashBang注释,用于在shell中执行js时,指定js解释器
是单行注释,只能写在js代码的第一行;MDN
#!/usr/bin/env node
console.log("Hello world");
error实例中cause属性,指示了引起错误的具体原因;MDN
try {
connectToDatabase();
} catch (err) {
throw new Error('Connecting to database failed.', { cause: err });
}
不改变数组原始值,创建新数据作为返回值
相对于直接用[索引]
更新数组,with方法不改变原始值、返回新数组
const arr = [1, 2, 3, 4, 5];
console.log(arr.with(2, 6)); // [1, 2, 6, 4, 5]
console.log(arr); // [1, 2, 3, 4, 5]
2023-07-29 08:00:00
我在上周的文章《程序员一定要有自己的博客》中写了我博客工具链的5步:
当时只实现了其中1245步,经过两天的探索我完成了第3步,让这个工具链实现了完整的自动化,这篇文章大致介绍一下。
我的博客代码仓库使用Github托管,Github提供了一个十分有用的自动化工作流的工具:Github Actions。
Github Actions 可以定时执行我提供的脚本,通过脚本,实现了从Notion到代码仓库的更新。大致逻辑是这样的:
通过以上脚本,实现了 notion文章数据库到git仓库的更新。
脚本执行的频率是可以自由更改的,我偏向于损失一些时效性,保证环境稳定性,所以选择了一天一更新。
以上脚本可以在我的博客开源仓库中看到,链接在这里 和 这里。
由于我们的notion数据库字段、md文件的frontmatter配置很可能不同,我的脚本无法直接被你复用,有几个需要考虑变更的点:
你的github仓库首页 - settings - secrests and variables - actions
,参数名是NOTION_DATABASE_ID
和NOTION_KEY
,前者是你在Notion中文章所在数据库的id(可以通过copy link获得),后者是你开通的notion api的key(notion api的开通详见这个文档)。postToMDFile
中找到,逻辑比较简单,按照你的需求修改即可。getPosts
中。github仓库首页-settings-actions-general-workflow permission - 开启read and write permissions
这一步我使用的是Vercel工具,监听Git仓库的push事件,自动拉取代码更新,打包部署到网站。
这一步Vercel的配置很简单,推荐查看官方文档,不做赘述。
2023-07-28 08:00:00
浏览器原生的setTimeout(fn,timeout)函数的原理,是保证timeout时间后,将回调函数fn加入event loop的消息队列,不是保证timeout时间后一定执行。
回调函数加入的消息队列后,实际的执行时间要等到 调用栈中已有函数 + 消息队列已有消息执行完毕。
所以回调函数的实际执行时间与设定的timeout有偏差。
setInterval函数也同样有偏差。
以下实现了一个时间准确的定时轮询函数。
/**
* setClockInterval是时钟准确的定时轮询函数(原生setInterval时钟不准确)
* @param func 与原生setInterval参数一致
* @param interval 与原生setInterval参数一致
* @return timer 类型为number
*/
const timerMap = new Map()
export function setClockInterval(func: Function, interval: number) {
let start: number
let tick: number
let clockTimer: number | null
const timerId = Math.floor(Math.random() * 1e10)
const recurFunc = () => {
func()
const realExcuTime = new Date().getTime()
if (!start) {
start = realExcuTime
}
tick = tick || start
// bias是本次实际执行时间戳 与 理论执行时间戳tick之间的偏差
// bias一定>=0,因为浏览器的setTimeout setInterval只会准时或者延迟执行,不会提前执行
// 经过本地浏览器统计 bias平均值在10ms以内,最大值在20ms左右
const bias = realExcuTime - tick
console.log(`实际执行时间戳${realExcuTime},理论计划执行时间戳${tick},本次偏差${bias}ms`);
// 下次tick时间戳
tick += interval
// 因为本次实际执行时间有bias的延迟,所以下次执行提前
clockTimer = window.setTimeout(recurFunc, interval - bias)
timerMap.set(timerId, clockTimer)
}
recurFunc()
return timerId
}
/**
* clearClockInterval 用于终止 setClockInterval 的定时轮询
* @param timer 是 setClockInterval 的返回值
*/
export function clearClockInterval(timerId: number) {
if (timerMap.has(timerId)) {
window.clearTimeout(timerMap.get(timerId))
} else {
console.warn(`timerId${timerId}not found`);
}
}
bias平均值在10ms以内,最大值在20ms左右
09:45:39.557 util.ts:23 实际执行时间戳1690508739557,理论计划执行时间戳1690508739550,本次偏差7ms
09:45:40.555 util.ts:23 实际执行时间戳1690508740555,理论计划执行时间戳1690508740550,本次偏差5ms
09:45:41.561 util.ts:23 实际执行时间戳1690508741561,理论计划执行时间戳1690508741550,本次偏差11ms
09:45:42.559 util.ts:23 实际执行时间戳1690508742559,理论计划执行时间戳1690508742550,本次偏差9ms
09:45:43.559 util.ts:23 实际执行时间戳1690508743559,理论计划执行时间戳1690508743550,本次偏差9ms
09:45:44.560 util.ts:23 实际执行时间戳1690508744560,理论计划执行时间戳1690508744550,本次偏差10ms
09:45:45.556 util.ts:23 实际执行时间戳1690508745555,理论计划执行时间戳1690508745550,本次偏差5ms
09:45:46.552 util.ts:23 实际执行时间戳1690508746552,理论计划执行时间戳1690508746550,本次偏差2ms
09:45:47.562 util.ts:23 实际执行时间戳1690508747562,理论计划执行时间戳1690508747550,本次偏差12ms
09:45:48.561 util.ts:23 实际执行时间戳1690508748561,理论计划执行时间戳1690508748550,本次偏差11ms
2023-07-19 08:00:00
最近用astro对自己的博客进行了重构,投入了很大的热情和很多的时间,导致耽误了这个月的读书任务,于是反思了一下是否值得这么做。
博客之于程序员意义重大,因为,输出是一个程序员的基本素养和技能,开源分享是科学技术发展和人类社会进步的重要美德和推动力,而博客正是输出分享的重要途径之一。
为什么输出分享对程序员来说十分重要,我有以下几点看法。
从技术角度考虑,技术的提升依赖于专业知识的学习以及实际经验的积累,而人的大脑需要不断地进行重复记忆,才能将这些知识经验留在自己的知识库里。因此,无论是学习新知识还是通过实践得到的经验,有了即时的输出记录,才便于复盘与巩固。
在实际工作中,当我们面临复杂大型的项目,和其中大量的代码时,如果只根据个人喜好而不根据代码规范去开发,写完后不留存开发文档以及接口文档,必然给项目的维护带来更高的代价,给团队中的其他开发者带来困扰。因此,代码规范和开发文档的输出也相当必要。
如果你还不相信,坚持输出给程序员带来好处的例子很多,我随意举几个。
例如 IT 圈出名的阮一峰,张鑫旭,stormzhang(这位已经不再分享技术了)等等大佬,他们有今天这种影响力的一个重要因素就是输出分享;
我也曾不止一次收到前辈的建议,前端圈的小爝大佬在知乎的某个回答当中提到过“长期坚持技术输出和总结分享”在找工作面试中是一个亮点和加分项;
“前端桃园”公号的运营者桃翁也十分提倡坚持输出,他在他星球小圈子中给我们分享过他自己 因为坚持输出提升了影响力 而多次收到阿里面试邀请的经历。
还有很多例子,不一一例举,分享本文的原因也就写到这,不再赘述。
(当然输出分享的途径很多,本文以介绍博客为目的,如果选择其他途径可以忽略下文)
现在网络上有各式各样的博客,有基于第三方的平台(如博客园、csdn 等),自带用户和流量,但是要受平台约束和限制;
也有可供个人搭建的工具(如 Hexo、Ghost、wordpress、jekyll 等),自由度高,但是需要从0开始搭建和积累流量;
选择一个最适合自己的最重要。
作为一个博客,我最重视的有两个方面:
因为每个人追求的博客风格不同,想要展示的内容和格式也有所不同。
我希望能自由地表达自己想说的话。
我指的转化,是你的输出原文档 到 博客文本的转化,这一转化的过程对于想要存档原文,或不习惯于博客编辑器,不习惯于博客网页格式的人之分重要。
我推崇用 markdown 来写笔记,因为 markdown 足够简约优雅,兼容性也十分强,
Markdown 是一种轻量级的「标记语言」,通常为程序员群体所用,目前它已是全球最大的技术分享网站 GitHub 和技术问答网站 StackOverFlow 的御用书写格式。
非技术类笔记用户,千万不要被「标记」、「语言」吓到,Markdown 的语法十分简单,常用的标记符号不超过十个,用于日常写作记录绰绰有余,不到半小时就能完全掌握。
就是这十个不到的标记符号,却能让人优雅地沉浸式记录,专注内容而不是纠结排版,达到「心中无尘,码字入神」的境界。
总结来说,我目标的工具链是这样的:
目前已经实现了1,2,4,5,目前正在研究第3步,成功后我就不会再对工具链投入时间,而是把注意力放到内容上。
以技术为目标的人,切忌浮躁。
莫逞他人嘴上快,莫争浮世虚功名,心无旁骛,沉下心来钻研技术就好。