添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
性感的毛豆  ·  Why am I getting ...·  7 月前    · 
慷慨的黄瓜  ·  zymad - Keyword ...·  7 月前    · 
朝气蓬勃的伤疤  ·  Using OPC UA with ...·  11 月前    · 
(opens new window)

# 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"

参考:

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。

理解 CRLF,LF (opens new window)

# 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 回复

Is Turbopack really 10x Faster than Vite? · Discussion #8 · yyx990803/vite-vs-next-turbo-hmr (opens new window)

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 链接

fix(shared): parse multi-line inline style by sxzz · Pull Request #6777 · vuejs/core (opens new window)

regex-s-flag.png

如下图,再没有修复这个 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)

find_x3_section1-2.gif

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、星空背景动画

find_x3_section-3.gif

<canvas id="zn-starry-star" width="1440" height="969"></canvas>

代码为 canvas,通过 F12 查看 source,搜索 js 文件里面的 zn-starry-star 关键字,找到核心代码

find_x3_section_star_source.png

查找前后代码,如下 https://www.oppo.com/content/dam/oppo/product-asset-library/find-x3-series/fussi-mars/v1/main-v3.js (opens new window)

关键字: 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

find-x3-mars-orange-dot-animation.gif

<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 算法教程

宫水三叶

其他

# 怎么通过前端 js 代码计算页面的 fps

在看 揭秘 Vue.js 九个性能优化技巧 (opens new window) 这边文章时,发现 演示项目 (opens new window) 右上角有一个实时显示页面 fps 的功能,如下图

fps.gif

这个功能时怎么实现的呢?查看源码后发现,是引入了一个 fps-indicator js 库,使用方法如下

// src/main.js
import fps from "fps-indicator";
fps({
  position: "top-right",
  style: `
    font-size: 24px;
});

核心实现代码,通过触发 requestAnimationFrame 函数来计算 fps,基本上执行一次该函数就是一帧

//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 命令

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 更加语义化,功能更简洁一点,对用户更友好。

参考:

# eslint+prettier 自动修复 template 时,元素换行异常问题解决

下面这行代码在保存后自动 fix 时, el-button 的结束标签换行异常

prettier自动fix后元素结束标签换行异常问题.png

<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 项目,再重新打开,配置才会生效

prettier配置修改后效果.png

上次更新: 2023/2/25 00:53:24