# 2023 年 02 月
# 2023-02-24
# vue2 升级 vue3 后,watch 死循环问题
import {watch} from 'vue'
watch(formData, () => {
console.log('watch formData')
// 一些操作,不知道为啥,里面的操作在 vue2 中正常,但 vue3 会导致死循环
}, {immediate: true, deep: true} )
vue3 响应式数据改为 proxy 之后,watch formData 出现了循环调用的问题?暂时未找到具体原因。临时解决方法
import {watch} from 'vue'
watch(formData, (val, oldVal) => {
console.log('watch formData')
// 发现循环触发时,新旧数据一致。可能是 formData.value = xxx 重复触发,导致死循环
// 这里加一个等值判断,如果新旧值一样,就不继续执行
if (JSON.stringify(val) === JSON.stringify(oldVal)) {
console.log('值相同,不继续执行')
return
// 一些操作,不知道为啥,里面的操作在 vue2 中正常,但 vue3 会导致死循环
{ immediate: true, deep: true}
# element-plus el-select remote-method 在失去焦点后也会触发的问题
vue2 版本在失去焦点的时候,不会触发 remote-methods,vue3 后,这个算是新增的行为,之前看过一个 issue,
印象中是为了解决某个场景下的值问题,但不是很有必要。
我们业务代码之中做了一个骚操作,就是点击 el-select 后,会隐藏弹窗下拉框(display: none)。显示自己自定义的一个选择框。
但是发现选中自定义选择部分的内容时,第一次会闪一下,排查了很久,发现是失去焦点后触发了 remote-methods,导致列表数据更新,选择框内容刷新了一遍。
解决方法:remote-method 中加一个判断,判断焦点是否在输入框中
<template>
<el-select
v-model="someVal"
ref="selectRef"
remote
filterable
:remote-method="remoteMethod"
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
</el-select>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const someVal = ref("");
const selectRef = ref();
const options = [
value: "Option1",
label: "Option1",
value: "Option2",
label: "Option2",
const remoteMethod = (query: string) => {
console.log("remoteMethod", query);
// 焦点判断,如果是失去焦点,不执行,关于焦点,可以搜索 focus, hasFocus
const hasFocus = selectRef.value.$el.contains(document.activeElement);
if (!hasFocus) {
console.log("失去焦点不做处理");
return;
// 正常逻辑
console.log("正常逻辑");
</script>
# vue3 data hasOwnProperty 非响应式问题
vue2 代码
<template>
<div :class="[testOgj.hasOwnProperty('a') ? 'true-class' : 'false-class']"></div>
</template>
<script>
export default {
data() {
return {
testObj: { a: 1 }
</script>
升级到 vue3 之后,testObj 数据变成 ref 后,更改 class 的操作 hasOwnProperty 变得不是响应式了。
修改为 testOgj['a'] 这种写法才 ok。
# 2023-02-20
# vue2/vue3/react源码,面试题,面经
https://whf293.github.io/ (opens new window)
# Syntax Error: TypeError: Cannot read property 'content' of null
vue 组件内两个 script 标签导致该错误
<script lang="ts" setup></script>
<!-- 忘记加 lang="ts" 了, 出现上面的错误,加上就好了 -->
<script></script>
# element-plus 糟糕的提示问题?ElOnlyChild 问题
ElmenetPlusError: [ElOnlyClid] no valid child node found,并没有提示具体是哪个组件的问题
导致,对于复杂的项目,代码量大的场景,都不知道具体是哪个组件报的这个异常,需要在大片的代码里面去排查,
比较耗时。提个 PR 改进?这种报错,提示粒度到组件?
<el-tooltip><div v-if="xx">123</div></el-tooltip>
# 2023-02-17
# vue3 bug ?activePreFlushCbs.length 报错
在项目中,一个弹窗关闭的场景,vue3 销毁时,修改某个值,activePreFlushCbs.length 报异常,activePreFlushCbs 为 null
没有加 ?. 空值判断,待复现
# 2023-02-15
# 关于设计感
soybean-admin (opens new window) A fresh and elegant admin template, based on Vue3,Vite3,TypeScript,NaiveUI and UnoCSS [一个基于Vue3、Vite3、TypeScript、NaiveUI 和 UnoCSS的清新优雅的中后台模版]
# vue2 升级 vue3,element-plus 的一些破坏性更新
date 格式问题
1、value-format="yyyy-MM-dd" 代码需要改为 value-format="YYYY-MM-DD"
参考:
- https://element.eleme.io/#/zh-CN/component/date-picker (opens new window)
- https://element-plus.gitee.io/zh-CN/component/date-picker.html (opens new window)
2、el-popover v-mode 变为 v-model:visible,el-dialog 的 :visible.sync 变更为 v-model
3、tab-click 行为变更,加了 await, 与 vue2 行为不一致,需要改为 tab-change
4、el-form rules 和 el-form-item rules 合并规则变更,原先是item覆盖form规则,现在是合并,都会生效。
5、icon 变更为 svg 后,改动较大 el-icon 几乎全部失效。
# sourcemap 还原位置有偏移?windows 与 linux 环境生成 soucemap 不一致的问题
在巡检线上异常时,发现有错误,本地生成 source map 后,还原,发现还原不了,定位到具体函数,发现位置有偏移。
问了下同事,同事用相同的方法测是正常的,于是让同事将生成的 map 文件发我
使用 git 来比对生成的 js、map 文件发现有 1-2 行区别,windows 是
\r\n
,同事 mac 是
\n
,但在 vscode
开发时,开发者时无感知的(可以在底部设置 LF、CRLF),注意 map 文件生成,最好在更接近生产的 linux 环境或 mac 环境,不要用 windows 生成。
CRLF, LF 是用来表示文本换行的方式。CR(Carriage Return) 代表回车,对应字符 '\r';LF(Line Feed) 代表换行,对应字符 '\n'。由于历史原因,不同的操作系统文本使用的换行符各不相同。主流的操作系统一般使用CRLF或者LF作为其文本的换行符。其中,Windows 系统使用的是 CRLF, Unix系统(包括Linux, MacOS近些年的版本) 使用的是LF。
# 2023-02-14
# vue-router 4.x 相比 3.x watch route 行为变更问题
vue3(对应vue-rotuer 4.x) setup 写法中、watch route 新旧值都是一样的,但在 vue2 版本(对应 3.x)中是好的。
<!-- xxx.vue -->
<script lang="ts" setup>
import { useRoute } from 'vue-router'
const route = useRoute()
watch(route, (newVal, oldVal) => {
console.log(newVla, oldVal) // 同一个页面,路由变更(hash)时,两个值一样
看了下文档,在 vue3 中,vue-router 有对应的 composition API, onBeforeRouteUpdate
import { onBeforeRouteUpdate } from 'vue-router'
import { ref } from 'vue'
export default {
setup() {
// 与 beforeRouteUpdate 相同,无法访问 `this`
onBeforeRouteUpdate(async (to, from) => {
//仅当 id 更改时才获取用户,例如仅 query 或 hash 值已更改
if (to.params.id !== from.params.id) {
userData.value = await fetchUser(to.params.id)
# 2023-02-13
# TS video 实例变量不存在 pause() 方法,对应类型怎么查找
搜索 video MDN,可以找到对于的类型 HTMLMediaElement
# 2023-02-18
# 前端 nginx 配置负载均衡,ip黑名单等
(三)Nginx一网打尽:动静分离、压缩、缓存、黑白名单、跨域、高可用、性能优化...想要的这都有! (opens new window)
# 看英文文档方法
掘金高赞
我只是用了个“笨”方法,一个月后不再惧怕英文文档 (opens new window)
# 2023-02-06
# 什么是 Turopack,号称比 vite 快 10 倍,尤雨溪发长文回怼
Turbopack is an incremental bundler optimized for JavaScript and TypeScript, written in Rust by the creators of webpack and Next.js(opens in a new tab) at Vercel(opens in a new tab).
On large applications Turbopack updates 10x faster than Vite and 700x faster than webpack . For the biggest applications the difference grows even more stark with updates up to 20x faster than Vite.
实际快 2 倍不到,Evan You 回复
https://twitter.com/youyuxi/status/1587279357885657089 (opens new window)
# Rust Is The Future of JavaScript Infrastructure
Rust Is The Future of JavaScript Infrastructure (opens new window) , 作者是 Vercel 的开发者关系主管。
中文翻译: Rust 是 JavaScript 基础设施的未来 (opens new window)
Turbopack
参考
# 一个 dialog 占 1G 内存?vue3 的一个内存泄漏bug
[Component] [dialog] dialog组件导致内存泄漏 (opens new window)
vue 相关问题?
The onUnmounted callback is not triggered when using Teleport
https://github.com/vuejs/core/issues/6347 (opens new window)
# 什么是 swc? parcel 2 使用它替代 js 打包性能提升 10 倍?
SWC (stands for Speedy Web Compiler) is a super-fast TypeScript / JavaScript compiler written in Rust.
Rust-based platform for the Web,目标:Make the web (development) faster.
Parcel 2 beta 3 (opens new window)
10x faster JavaScript compiler written in Rus
# smooth-scrolling 平滑滚动产生的 bug
table 纵向懒加载时候,默认滚动到最下面,需要滚动到之前的位置,直接修改 scrollTop 会触发平滑滚动
但我的电脑有点问题,平滑滚动效果是关的,导致滚动有问题,怎么在滚动的时候关闭平滑滚动效果,以及怎么配置浏览器开启平滑滚动效果
chrome://flags/#enable-smooth-scrolling
滚动时,不触发平滑滚动
element.scrollTo({
top: 100,
left: 100,
behavior: 'smooth' // smooth (平滑滚动),instant (瞬间滚动),默认值 auto,效果等同于 instant
});
# 通过 vue3 的两个历史 bug,来了解正则 s 标志位的用处
正则表达式格式为
/pattern/flags
, flags 标志位:g 不仅仅匹配第一个,全局匹配。i 忽略大小写。
根据 vuejs 的一个 bug 来看 s 标志位的应用场景。
s 标志位的使用场景:
-
正则表达式的模式匹配中,
.
默认可以匹配除\n
外的任意一个字符。 -
如果加了
/s
标志位,.
就可以匹配全部字符了,包括\n
参考: 正则表达式 - 修饰符(标记) (opens new window)
vue3 的源码中,有一个
.
无法匹配换行符 (
\n
) 导致的 bug,下面来看看,以下是 PR 链接
如下图,再没有修复这个 bug 前,下面的多行内联样式,在
padding-box,
位置会结束,后面的内容会匹配不到
<div style="
border: 1px solid transparent;
background: linear-gradient(white, white) padding-box,
repeating-linear-gradient(
-45deg,
#ccc 0,
#ccc 0.5em,
white 0,
white 0.75em
</div>
之前使用的正则是,
(.+)
匹配不到换行
const propertyDelimiterRE = /:(.+)/
中间 尤雨溪 加了如下改动,链接
const propertyDelimiterRE = /:(.+)/s
// 删除上面这一行,这里加了 s 标志位,但是有位置的单元测试过不去,
// 最后改为下面这一行,链接 https://github.com/vuejs/core/pull/6777/commits/2a50d03e5c6a737a860de349e0df16743c654582
const propertyDelimiterRE = /:([^]+)/
文件地址: https://github.com/vuejs/core/blob/main/packages/shared/src/normalizeProp.ts (opens new window)
再来看下面一个 bug,vue 3.2.40 版本中 style 内联样式中注释下面的一行样式会不生效。 "Inline style regular doesn't work when below comments · Issue #6807 · vuejs/core" (opens new window)
<div
style="
/* something */
width: 300px;
height: 300px;
background-color: pink;
></div>
解决方法如下,使用正则匹配 style 中的评论,然后删除,也用到了额 s 标志位
const styleCommentRE = /\/\*.*?\*\//gs
// /* .*? */
export function parseStringStyle(cssText: string): NormalizedStyle {
const ret: NormalizedStyle = {}
cssText
.replace(styleCommentRE, '') // 将 style 内容中的注释内容删除
.split(listDelimiterRE)
.forEach(item => {
if (item) {
const tmp = item.split(propertyDelimiterRE)
tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim())
return ret
# 2023-02-05
# Find X3 火星版官网动效实现
OPPO Find X3 Pro 火星探索版 有色彩的地方 就有让生命感动的力量 | OPPO 官方网站 (opens new window)
效果预览: nice.zuo11.com (opens new window)
1、首屏是一个 video 动画,没有加 loop,比较简单
2、第二屏也是一个 video 视频,加了鼠标样式,文字动画
修改鼠标样式代码如下
/*
<div class="section-2" style="width: 100%">
<video src="xxx" muted autoplay loop style="width: 100%" ></video>
<!-- @鼠标样式_start -->
<div class="player-btn">
<div>鼠标样式图片</div>
<span class="text">观看完整视频</span>
let section2El = document.querySelector(".section-2");
let playBtnEl = document.querySelector(".player-btn");
section2El.addEventListener("click", (e) => {
// alert("播放视频");
});
window.addEventListener(
"mousemove",
_.throttle((e) => {
// 鼠标相对页面的位置
let x = e.pageX;
let y = e.pageY;
// section-2 容器相对视口位置
let parentX = section2El.offsetLeft;
let parentY = section2El.offsetTop;
let isYOut = y < parentY || y > parentY + section2El.clientHeight;
let isXOut = x < parentX || x > parentX + section2El.clientWidth;
if (isXOut || isYOut) {
// console.log("移出去了");
playBtnEl.style.opacity = "0";
return;
} else {
playBtnEl.style.opacity = "1";
// 鼠标不在正中心,需要减去鼠标样式区域的宽高才能达到居中效果
playBtnEl.style.left = `${x - parentX - playBtnEl.clientWidth / 2}px`;
playBtnEl.style.top = `${y - parentY - playBtnEl.clientHeight / 2}px`;
}, 50)
文字也有一个过渡动画,通过 clip-path 进行显示隐藏切换,再加上 transition 过渡
/* 初始化时,默认隐藏文字 */
.section-2-text .title {
transition: all 0.7s; /* 当 css 属性发生变化时,0.7s 内完成变更 */
/* 裁剪路径,inset 显示这个区域的内容,right、left 50%,会隐藏元素 */
clip-path: inset(0 50% 0 50%);
/*滚动到当前区域时,增加 class*/
通过 gsap 判断滚动距离,当这个区域滚动到视口中间(center)时,触发 class 添加。有 transition 就形成了动画
// 到视口中间时,父元素添加一个 fade-in 样式,显示该元素
.section-2.fade-in .title {
clip-path: inset(0 0 0 0); /* 滚到到视口时,显示元素 */
// gsap 滚动处理
gsap.registerPlugin(ScrollTrigger);
gsap.to(".section-2-text", {
opacity: 1,
scrollTrigger: {
trigger: ".section-2",
start: "top center", // 当元素顶部部,滚动到达视口中间时, 开始动画
// end 默认是 trigger 离开视口
toggleClass: "fade-in",
scrub: true, // 表示动画可以重复执行改成false表示只执行一次
// markers: true, // 绘制开始位置和结束位置的线条
// pin: false, // 动画执行期间,页面不进行滚动,动画执行结束后
});
3、星空背景动画
<canvas id="zn-starry-star" width="1440" height="969"></canvas>
代码为 canvas,通过 F12 查看 source,搜索 js 文件里面的 zn-starry-star 关键字,找到核心代码
关键字: vec4 星空动画 vec4 canvas 星空动画 vec4 canvas 星空动画
关键字: three.js 星空,搜索到一个比较接近的星空效果,地址: three.js 制作星空 (opens new window)
<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>3Dstar</title>
<style>
body {
margin: 0;
overflow: hidden;
</style>
<script src="./threejs/three.min.js"></script
<script src="./threejs/OrbitControls.js"></script>
建议保存到本地, 文件链接:
https://cdn.bootcdn.net/ajax/libs/three.js/0.149.0/three.min.js
http://nice.zuo11.com/3-find-x3-mars/threejs/OrbitControls.js
</head>
<script>
* 创建场景对象Scene
var scene = new THREE.Scene();
var intersectsArr = [];
//星空背景
var cloud = cloudFun();
scene.add(cloud);
function cloudFun() {
var geom = new THREE.Geometry();
var material = new THREE.ParticleBasicMaterial({
size: 2,
vertexColors: true,
});
var n = 1200;
for (var i = 0; i < 3000; i++) {
var particle = new THREE.Vector3(
(Math.random() - 0.5) * n,
(Math.random() - 0.5) * n,
(Math.random() - 0.5) * n
geom.vertices.push(particle);
let color_k = Math.random();
// 蓝白色
// geom.colors.push(new THREE.Color(color_k, color_k, color_k * 2.0));
// 橙色为RGB为255,165,0,代码#FFA500
geom.colors.push(new THREE.Color(color_k * 10, color_k * 3, color_k));
var cloud = new THREE.ParticleSystem(geom, material);
return cloud;
* 透视投影相机设置
var width = window.innerWidth; //窗口宽度
var height = window.innerHeight; //窗口高度
/**透视投影相机对象*/
var camera = new THREE.PerspectiveCamera(60, width / height, 1, 1000);
camera.position.set(651, 613, 525); //设置相机位置
camera.lookAt(scene.position); //设置相机方向(指向的场景对象)
* 光源设置
//点光源
var point = new THREE.PointLight(0xffffff);
point.position.set(800, 200, 300);
scene.add(point);
// 点光源2 位置和point关于原点对称
var point2 = new THREE.PointLight(0xffffff);
point2.position.set(0, -500, 0); //点光源位置
scene.add(point2); //点光源添加到场景中
//环境光
var ambient = new THREE.AmbientLight(0x000000);
scene.add(ambient);
* 创建渲染器对象
var renderer = new THREE.WebGLRenderer({
antialias: true,
});
renderer.setSize(width, height); //设置渲染区域尺寸
renderer.setClearColor(0x101010, 1); //设置背景颜色
document.body.appendChild(renderer.domElement); //body元素中插入canvas对象
let clock = new THREE.Clock();
var FPS = 30;
var refreshTime = 1 / FPS;
var timeS = 0;
function render() {
var renderInterval = clock.getDelta();
timeS = timeS + renderInterval;
if (timeS > refreshTime) {
//执行渲染操作
renderer.render(scene, camera);
timeS = 0;
//每次渲染位置变化,动态效果
cloud.rotation.x += 0.0002;
cloud.rotation.y += 0.0002;
cloud.rotation.z += 0.0002;
//周期性渲染
requestAnimationFrame(render);
render();
var controls = new THREE.OrbitControls(camera); //创建控件对象
</script>
</body>
</html>
three.js 星空在线预览 (opens new window)
相关链接:
gsap 逻辑与 clip-path 逻辑
- 内容分为 5 个部分,每个部分都是重叠的,初始都是隐藏的,opacity为 0, clip-path 左边裁切 100%(不显示)
- 当滚动到对应的区域时,增加 active 属性,opacity 过渡到 1, clip-path 右裁切过渡到0 (不裁剪,完全显示)
- 第一个动画结束后,onLeave 钩子里,开启下一个场景 active 添加。onEnter 钩子里面切换当前进度百分比显示
- 整体的 pin 使用了父元素 pin,end 为滚动到 3000px 后,才结束固定。(屏幕适配可能会有问题,待优化)
<style>
.section-3-info .part img,
.left-desc .text-title,
.left-desc .text-detail {
clip-path: inset(0 0 0 100%); /* 左侧裁剪 100% => 左侧裁剪 0%, 从右到左*/
opacity: 0;
.section-3-info .part img {
transition: clip-path 0.8s, opacity 0.8s;
.left-desc .text-title {
transition: clip-path 0.4s, opacity 0.4s;
.left-desc .text-detail {
transition: clip-path 0.4s 0.2s, opacity 0.6s;
.part.active .text-title,
.part.active .text-detail,
.part.active img {
opacity: 1;
clip-path: inset(0 0 0 0);
</style>
<script>
const changeProgress = (val) => {
document.querySelector("#progress").innerHTML = `${val}%`;
let tl = gsap.timeline();
tl.to(".part-1", {
opacity: 1,
scrollTrigger: {
trigger: ".part-1",
start: "top top",
end: "+1000",
toggleClass: "active",
scrub: true, // 表示动画可以重复执行改成false表示只执行一次
// markers: true, // 绘制开始位置和结束位置的线条
onEnter: function () {
changeProgress(33);
onEnterBack: function () {
changeProgress(33);
onLeave: function () {
gsap.to(".part-2", {
opacity: 1,
scrollTrigger: {
trigger: ".part-2",
start: "top top",
end: "+1000",
toggleClass: "active",
scrub: true, // 表示动画可以重复执行改成false表示只执行一次
// markers: true, // 绘制开始位置和结束位置的线条
onEnter: function () {
changeProgress(66);
onEnterBack: function () {
changeProgress(66);
onLeave: function () {
gsap.to(".part-3", {
opacity: 1,
scrollTrigger: {
trigger: ".part-3",
start: "top top",
end: "+1000",
toggleClass: "active",
scrub: true, // 表示动画可以重复执行改成false表示只执行一次
// markers: true, // 绘制开始位置和结束位置的线条
onEnter: function () {
changeProgress(100);
onEnterBack: function () {
changeProgress(100);
onLeave: () => {
// gsap.set('.part-2', {autoAlpha: 0});
});
});
});
// 固定容器
gsap.to(".section-3", {
opacity: 1,
scrollTrigger: {
trigger: ".section-3",
start: "top top", // 当元素顶部部,滚动到达视口中间时, 开始动画
// end 默认是 trigger 离开视口
end: "+3000", // 当section-2底部,到达可视区域bottom 500px时,结束动画
// scrub: true, // 表示动画可以重复执行改成false表示只执行一次
// markers: true, // 绘制开始位置和结束位置的线条
pin: true, // 动画执行期间,页面不进行滚动,动画执行结束后
onEnterBack() {
document.querySelector('.abs.s-4-2').classList.remove('active')
document.querySelector('.comp-inner').classList.remove('active')
});
</script>
小黄点定位 css animation
<style>
@keyframes circleScale {
from {
transform: scale(2);
transform: scale(0.5);
@keyframes circleScaleSlow {
from {
transform: scale(1);
transform: scale(0.5);
.circle-wrapper .fast-flow,
.circle-wrapper .slow-flow {
width: 100%;
height: 100%;
position: absolute;
opacity: 1;
border: 1px solid #ff995e;
background: transparent;
border-radius: 50%;
.circle-wrapper .fast-flow {
animation: circleScale 1.5s ease infinite;
.circle-wrapper .slow-flow {
animation: circleScaleSlow 1.5s ease infinite;
/* 中心小黄点 */
.circle-wrapper .circle {
width: 8px;
height: 8px;
border-radius: 50%;
background: #ff995e;
margin-top: 50%;
margin-left: 50%;
transform: translate(calc(-50% + 1px), calc(-50% + 1px));
</style
<!-- 所在位置 点 动画-->
<div class="circle-wrapper abs circle1">
<div class="fast-flow"></div>
<div class="slow-flow"></div>
<div class="circle"></div>
</div>
</body>
其他场景代码参见: https://github.com/zuoxiaobai/nice-func/tree/main/src/3-find-x3-mars (opens new window)
# 2023-02-02
# 关于 vue3 composition 与 utils 功能的区分界定
一般 vue3 composition 里面也可以写纯 js 逻辑,但建议仅当包含 vue 相关代码时,才使用 composition use-xx 来定义。
对于纯 js 逻辑或者 dom 操作,使用 utils 就行。
比如下面这种包含 vue 方法的,可以使用 composition 格式定义
import { ref } from "vue";
export const useXxx = () => {
const foo = ref("test");
return { foo };
针对下面的纯 js 逻辑,建议放到 utils 分类中
const xxx = () => {
document.querySelector("xxx").xx; // 一些 dom 操作
// 其他 js 逻辑等
# 关于算法系统化学系、刷题
hello 算法教程
- 第一步:算法快速入门 Hello 算法 - 多语言示例-插图/动图设计感好 - 适合入门 (opens new window)
- 第二步:按计划刷题 剑指 Offer》刷题计划 (opens new window)
宫水三叶
- 宫水三叶的刷题日记 (opens new window)
- wiki 里面有分类、难易度标记 (opens new window)
其他
# 怎么通过前端 js 代码计算页面的 fps
在看 揭秘 Vue.js 九个性能优化技巧 (opens new window) 这边文章时,发现 演示项目 (opens new window) 右上角有一个实时显示页面 fps 的功能,如下图
这个功能时怎么实现的呢?查看源码后发现,是引入了一个
fps-indicator
js 库,使用方法如下
// src/main.js
import fps from "fps-indicator";
fps({
position: "top-right",
style: `
font-size: 24px;
});
核心实现代码,通过触发 requestAnimationFrame 函数来计算 fps,基本上执行一次该函数就是一帧
- raf (opens new window) ,可以直接理解为 requestAnimationFrame,封了一层,做了一些兼容性处理
//enable update routine
var that = this;
raf(function measure() {
count++;
var t = now();
if (t - lastTime > period) {
lastTime = t;
values.push(count / (max * period * 0.001));
values = values.slice(-w);
count = 0;
ctx.clearRect(0, 0, w, h);
ctx.fillStyle = getComputedStyle(that.canvas).color;
for (var i = w; i--; ) {
var value = values[i];
if (value == null) break;
ctx.fillRect(i, h - h * value, 1, h * value);
that.valueEl.innerHTML = (values[values.length - 1] * max).toFixed(1);
raf(measure);
});
完整代码
/**
* @module fps-indicator
var raf = require("raf");
var now = require("right-now");
var css = require("to-css");
module.exports = fps;
function fps(opts) {
if (!(this instanceof fps)) return new fps(opts);
if (typeof opts === "string") {
if (positions[opts]) opts = { position: opts };
else opts = { container: opts };
opts = opts || {};
if (opts.container) {
if (typeof opts.container === "string") {
this.container = document.querySelector(opts.container);
} else {
this.container = opts.container;
} else {
this.container = document.body || document.documentElement;
//init fps
this.element = document.createElement("div");
this.element.className = "fps";
this.element.innerHTML = [
'<div class="fps-bg"></div>',
'<canvas class="fps-canvas"></canvas>',
'<span class="fps-text">fps <span class="fps-value">60.0</span></span>',
].join("");
this.container.appendChild(this.element);
this.canvas = this.element.querySelector(".fps-canvas");
this.textEl = this.element.querySelector(".fps-text");
this.valueEl = this.element.querySelector(".fps-value");
this.bgEl = this.element.querySelector(".fps-bg");
var style = opts.css || opts.style || "";
if (typeof style === "object") style = css(style);
var posCss = "";
posCss = positions[opts.position] || positions["top-left"];
this.element.style.cssText = [
"line-height: 1;",
"position: fixed;",
"font-family: Roboto, sans-serif;",
"z-index: 1;",
"font-weight: 300;",
"font-size: small;",
"padding: 1rem;",
posCss,
opts.color ? "color:" + opts.color : "",
style,
].join("");
this.canvas.style.cssText = [
"position: relative;",
"width: 2em;",
"height: 1em;",
"display: block;",
"float: left;",
"margin-right: .333em;",
].join("");
this.bgEl.style.cssText = [
"position: absolute;",
"height: 1em;",
"width: 2em;",
"background: currentcolor;",
"opacity: .1;",
].join("");
this.canvas.width = parseInt(getComputedStyle(this.canvas).width) || 1;
this.canvas.height = parseInt(getComputedStyle(this.canvas).height) || 1;
this.context = this.canvas.getContext("2d");
var ctx = this.context;
var w = this.canvas.width;
var h = this.canvas.height;
var count = 0;
var lastTime = 0;
var values = opts.values || Array(this.canvas.width);
var period = opts.period || 1000;
var max = opts.max || 100;
//enable update routine
var that = this;
raf(function measure() {
count++;
var t = now();
if (t - lastTime > period) {
lastTime = t;
values.push(count / (max * period * 0.001));
values = values.slice(-w);
count = 0;
ctx.clearRect(0, 0, w, h);
ctx.fillStyle = getComputedStyle(that.canvas).color;
for (var i = w; i--; ) {
var value = values[i];
if (value == null) break;
ctx.fillRect(i, h - h * value, 1, h * value);
that.valueEl.innerHTML = (values[values.length - 1] * max).toFixed(1);
raf(measure);
});
var positions = {
"top-left": "left: 0; top: 0;",
"top-right": "right: 0; top: 0;",
"bottom-right": "right: 0; bottom: 0;",
"bottom-left": "left: 0; bottom: 0;",
# git switch 和 git checkout 都可以切换分支,有什么区别?
一般 git checkout 和 git switch 都可以切换分支,他们有什么区别?
Git 2.23 引入了 git switch 命令
- git-switch - Switch branches switch - git (opens new window)
- git-checkout - Switch branches or restore working tree files checkout - git (opens new window)
The first rule of Doug McIlroy’s UNIX Philosophy says:
Make each program do one thing well. To do a new job, build afresh rather than complicate old programs by adding new “features”.
"新增一个功能,最好创建新的功能模块,而不是通过在旧功能上新增一个补丁特性来实现"
git checkout does too many things ,has not followed this philosophy very closely.
大致意思是,git checkout 功能太复杂了,有 6 种用法。破坏的 git 用户体验
git switch 更加语义化,功能更简洁一点,对用户更友好。
参考:
- What's the Difference Between Git Switch and Checkout? (opens new window)
- Two Commits That Wrecked the User Experience of Git (opens new window)
# eslint+prettier 自动修复 template 时,元素换行异常问题解决
下面这行代码在保存后自动 fix 时, el-button 的结束标签换行异常
<el-button type="primary" text @click="deleteShortLink(scope.$index)"
>删除</el-button
Replace `>删除</el-button` with `⏎············>删除</el-button⏎··········`eslintprettier/prettier
这种情况需要修改 prettier 配置
// .prettierrc.json 或 .prettierrc.js
"htmlWhitespaceSensitivity": "ignore"
修改后,再保存就正常了, 注意:一般情况下,这里修改 prettier 配置后,需要关闭 vscode 项目,再重新打开,配置才会生效
技术日常记录 2023年01月