websocket通用封装
1 | import store from '@/store'; |
1 | import store from '@/store'; |
1 | npm install -S file-saver |
下载Blob.js和Export2Excel.js,在src目录下新建Excel文件夹,里面放入Blob.js和Export2Excel.js两个JS文件,下面有
1 | import Blob from './Excel/Blob' |
1 | //导出的方法 |
把Export2Excel.js里面require(‘script-loader!vendor/Blob’)改为 require(’./Blob.js’)
1 | /* eslint-disable */ |
1 | //Export2Excel.js |
localStorage 是前端本地存储的一种,其容量一般在 5M-10M 左右,用来缓存一些简单的数据基本够用,毕竟定位也不是大数据量的存储。
在某些场景下 localStorage 的容量就会有点捉襟见肘,其实浏览器是有提供大数据量的本地存储的如 IndexedDB 存储数据大小一般在 250M 以上。
弥补了localStorage容量的缺陷,但是使用要比localStorage复杂一些 mdn IndexedDB
不过已经有大佬造了轮子封装了一些调用过程使其使用相对简单,下面我们一起来看一下
localforage 拥有类似 localStorage API,它能存储多种类型的数据如 **Array ArrayBuffer Blob Number Object String**,而不仅仅是字符串。
这意味着我们可以直接存 对象、数组类型的数据避免了 JSON.stringify 转换数据的一些问题。
存储其他数据类型时需要转换成上边对应的类型,比如vue3中使用 reactive 定义的数据需要使用toRaw 转换成原始数据进行保存, ref 则直接保存 xxx.value 数据即可。
下载最新版本 或使用 npm bower 进行安装使用。
1 | 引入下载的 localforage 即可使用 |
提供了与 localStorage 相同的api,不同的是它是异步的调用返回一个 Promise 对象
1 | localforage.getItem('somekey').then(function(value) { |
getItem 根据数据的 key 获取数据 差不多返回 nullsetItem 根据数据的 key 设置数据(存储undefined时getItem获取会返回 null)removeItem 根据key删除数据length 获取key的数量key 根据 key 的索引获取其名keys 获取数据仓库中所有的 key。iterate 迭代数据仓库中的所有 value/key 键值对。完整配置可查看文档 这里说个作者觉得有用的
localforage.config({ name: 'My-localStorage' }); 设置仓库的名字,不同的名字代表不同的仓库,当一个应用需要多个本地仓库隔离数据的时候就很有用。
1 | const store = localforage.createInstance({ |
同时也支持删除仓库
1 | // 调用时,若不传参,将删除当前实例的 “数据仓库” 。 |
idb-keyval是用IndexedDB实现的一个超级简单的基于 promise 的键值存储。
npm npm install idb-keyval
1 | // 全部引入 |
浏览器直接引入 <script src="https://cdn.jsdelivr.net/npm/idb-keyval@6/dist/umd.js"></script>
暴露的全局变量是 idbKeyval 直接使用即可。
由于其没有中文的官网,会把例子及自己的理解附上
值可以是 数字、数组、对象、日期、Blobs等,尽管老Edge不支持null。
键可以是数字、字符串、日期,(IDB也允许这些值的数组,但IE不支持)。
1 | import { set } from 'idb-keyval'; |
一个设置多个值,比一个一个的设置更快
1 | import { set, setMany } from 'idb-keyval'; |
如果没有键,那么val将返回undefined的。
1 | import { get } from 'idb-keyval'; |
一次获取多个数据,比一个一个获取数据更快
1 | import { get, getMany } from 'idb-keyval'; |
根据 key 删除数据
1 | import { del } from 'idb-keyval'; |
一次删除多个键,比一个一个删除要快
1 | import { del, delMany } from 'idb-keyval'; |
因为 get 与 set 都是异步的使用他们来更新数据可能会存在问题如:
1 | // Don't do this: |
上述代码我们期望的是 2 但实际结果是 1,我们可以在第一个回调执行第二次操作。
更好的方法是使用 update 来更新数据
1 | // Instead: |
将自动排队更新,所以第一次更新将计数器设置为1,第二次更新将其设置为2。
1 | import { clear } from 'idb-keyval'; |
[key, value] 形式的数据1 | import { entries } from 'idb-keyval'; |
key1 | import { keys } from 'idb-keyval'; |
value1 | import { values } from 'idb-keyval'; |
文字解释:表 === store === 商店 一个意思
1 | // 自定义数据库名称及表名称 |
使用 createStore 创建的数据库一个库只会创建一个表即:
1 | // 同一个库有不可以有两个表,custom-store-2 不会创建成功: |
自己管理定制商店,这个没搞太明白,看文档中说既然都用到这个了不如直接使用idb 这个库
本文介绍了两个 IndexedDB 的库,用来解决 localStorage 存储容量太小的问题
localforage 与 idb-keyval 之间我更喜欢 localforage 因为其与 localStorage 相似的api几乎没有上手成本。
如果需要更加灵活的库可以看一下 dexie.js、PouchDB、idb、JsStore 或者 lovefield 之类的库
HTML5不是什么新鲜事。自初始版本(2008 年 1 月)以来,我们一直在使用它的几个功能。我再次仔细查看了 HTML5 功能列表。看看我发现了什么?到目前为止,我还没有真正使用过很多!
在本文中,我列出了 10 个这样的HTML5功能,这些功能过去我用得不多,但现在发现它们很有用。我还创建了一个工作示例流程并托管在GitHub. 希望你也觉得它有用。让我们开始了解有关它们中的每一个的解释、代码和快速提示。
该<details>标签向用户提供按需详细信息。如果您需要按需向用户显示内容,请使用此标签。默认情况下,小部件是关闭的。打开时,它会展开并显示其中的内容。
该<summary>标签用于<details>为它指定一个可见的标题。
代码
1 | <details> |
看看它如何工作
contenteditable是可以在元素上设置以使内容可编辑的属性。它适用于 DIV、P、UL 等元素。您必须指定它,例如,<element contenteditable="true|false">。
注意: 当contenteditable元素上没有设置属性时,它将从其父元素继承。
代码
1 | <h2> Shoppping List(Content Editable) </h2> |
看看它如何工作
快速提示
span 或 div 元素可以使用它进行编辑,您可以使用 CSS 样式向其中添加任何丰富的内容。这将比使用输入字段处理它要好得多。去试一试!
该<map>标签有助于定义图像映射。图像映射是其中包含一个或多个可点击区域的图像。地图标签带有一个<area>标签来确定可点击区域。可点击区域可以是这些形状、矩形、圆形或多边形区域之一。如果您不指定任何形状,它会考虑整个图像。
代码
1 | <div> |
看看它如何工作
快速提示
图像地图有其自身的缺点,但您可以将其用于视觉演示。试试看一张全家福怎么样,然后深入到个人的照片(可以是我们一直珍视的旧照片!)。
使用<mark>标签突出显示任何文本内容。
1 | <p> 你知道吗,你可以仅使用 HTML 标签 <mark>"突出显示有趣的东西"</mark></p> |
看看它如何工作
快速提示
您可以随时使用 css 更改高亮颜色
1 | mark { |
这些data-*属性用于存储页面或应用程序私有的自定义数据。存储的数据可用于 JavaScript 代码以创建进一步的用户体验。
data-* 属性由两部分组成:
代码
1 | <h2> Know data attribute </h2> |
然后在 JavaScript 中,
1 | function reveal() { |
注意:要在 JavaScript 中读取这些属性的值,您可以使用getAttribute()它们的完整 HTML 名称(即 data-custom-attr),但标准定义了一种更简单的方法:使用dataset属性。
看看它如何工作
快速提示
您可以使用它在页面上存储一些数据,然后使用 REST 调用将其传递给服务器。
<output>标签表示的运算的结果。通常,此元素定义将用于显示某些计算的文本输出的区域。
代码
1 | <form oninput="x.value=parseInt(a.value) * parseInt(b.value)"> |
看看它如何工作
快速提示
如果您在客户端 JavaScript 中执行任何计算,并且希望结果反映在页面上,请使用<output>标记。您不必执行使用 获取元素的额外步骤getElementById()。
<datalist>标签指定了一个预定义选项列表,并允许用户向其中添加更多选项。它提供了一项autocomplete功能,允许您通过预先输入获得所需的选项。
代码
1 | <form action="" method="get"> |
看看它如何工作
快速提示
它与传统<select>-<option>标签有何不同?选择标签用于从您需要浏览列表的选项中选择一项或多项。Datalist是具有自动完成支持的高级功能。
range是给定滑块类型范围选择器的输入类型。
代码
1 | <form method="post"> |
看看它如何工作
快速提示
HTML5 中没有叫slider的!
使用<meter>标签测量给定范围内的数据。
代码
1 | <label for="home">/home/atapas</label> |
看看它如何工作
快速提示
不要将<meter>标签用于进度指示器类型的用户体验。我们有来自 HTML5的<Progress>标签。
1 | <label for="file">Downloading progress:</label> |
这部分是我们最熟悉的输入类型的用法,如文本、密码等。输入类型的特殊用法很少
代码
必需的 将输入字段标记为必填字段。
1 | <input type="text" id="username1" name="username" required> |
自动对焦 通过将光标放在输入元素上自动提供焦点。
1 | <input type="text" id="username2" name="username" required autofocus> |
使用正则表达式验证 您可以使用正则表达式指定模式来验证输入。
1 | <input type="password" |
颜色选择器 一个简单的颜色选择器。
1 | <input type="color" onchange="showColor(event)"> |
1 | function Person() { |
在这个例子中,Person 就是一个构造函数,我们使用 new 创建了一个实例对象。person是实例对象
每个 函数 都有一个 prototype 属性。
1 | function Person() { |
原型定义:
每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型”继承”属性。
每一个JavaScript对象(除了 null )都具有的一个属性,叫__proto__,这个属性会指向该对象的原型
1 | function Person() { |
每个原型都有一个constructor 属性指向关联的构造函数。
1 | function Person() { |
以上就是 构造函数,原型和实例对象之间的关系。
综上我们已经可以得出:
1 | function Person() { |
当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。
1 | function Person() { |
在这个例子中,我们给实例对象 person 添加了 name 属性,当我们打印 person.name 的时候,结果自然为 chimmy。但是当我们删除了 person 的 name 属性时,读取 person.name,从 person 对象中找不到 name 属性就会从 person 的原型也就是 person.proto ,也就是 Person.prototype中查找,幸运的是我们找到了 name 属性,结果为 jimmy。
但是万一还没有找到呢?原型的原型又是什么呢?
原型也是一个对象,既然是对象,我们就可以用最原始的方式创建它。
1 | let obj = new Object(); |
其实原型对象就是通过 Object 构造函数生成的,结合之前所讲,实例的 proto 指向构造函数的 prototype ,所以我们更新下关系图
那 Object.prototype 的原型呢?是null,我们可以打印
1 | console.log(Object.prototype.__proto__ === null) // true |
所以查找属性的时候查到 Object.prototype 就可以停止查找了。null没有原型。
下图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。
constructor
1 | function Person() { |
当获取 person.constructor 时,其实 person 中并没有 constructor 属性,当不能读取到constructor 属性时,会从 person 的原型也就是 Person.prototype 中读取,正好原型中有该属性,所以:person.constructor === Person.prototype.constructor
真的是继承吗?
每一个对象都会从原型继承属性,实际上,继承是一个十分具有迷惑性的说法,引用《你不知道的JavaScript》中的话,就是: 继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
1 | 输入:nums = [2,7,11,15], target = 9 |
示例 2:
1 | 输入:nums = [3,2,4], target = 6 |
示例 3:
1 | 输入:nums = [3,3], target = 6 |
提示:
2 <= nums.length <= 104-109 <= nums[i] <= 109-109 <= target <= 109思路
使用冒泡法逐一与前一个相加,如果两数相加之和等于target则返回,反之继续
个人解答:
1 | /** |
给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。
如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] ,就返回 0。
假设环境不允许存储 64 位整数(有符号或无符号)。
示例 1:
1 | 输入:x = 123 |
示例 2:
1 | 输入:x = -123 |
示例 3:
1 | 输入:x = 120 |
示例 4:
1 | 输入:x = 0 |
提示:
-231 <= x <= 231 - 1思路
将参数转化为String类型,然后判断该参数是否大于0,如果大于0,则转化为数组再翻转转化为String类型,如果小于0,则现将负号剔除,进行前面操作
个人解答
1 | /** |
给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。
回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。例如,121 是回文,而 123 不是。
示例 1:
1 | 输入:x = 121 |
示例 2:
1 | 输入:x = -121 |
示例 3:
1 | 输入:x = 10 |
示例 4:
1 | 输入:x = -101 |
提示:
-231 <= x <= 231 - 1思路
如果是正数,则将数据化为数组、翻转,如果相等则true,反之false,如果为负数,则先将负号剔除,进行前面操作
个人解答
1 | /** |
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""。
示例 1:
1 | 输入:strs = ["flower","flow","flight"] |
示例 2:
1 | 输入:strs = ["dog","racecar","car"] |
提示:
0 <= strs.length <= 2000 <= strs[i].length <= 200strs[i] 仅由小写英文字母组成思路
使用冒泡法,将除数组第一个元素与数组第一各元素的各个子元素进行对比,如果相等则将子元素记录下来,反之则返回记录的数据
个人解答
1 | /** |
给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
示例 1:
1 | 输入:s = "()" |
示例 2:
1 | 输入:s = "()[]{}" |
示例 3:
1 | 输入:s = "(]" |
示例 4:
1 | 输入:s = "([)]" |
示例 5:
1 | 输入:s = "{[]}" |
提示:
1 <= s.length <= 104s 仅由括号 '()[]{}' 组成思路
首先建立一个Map对象,将括号放入其中,如果Map对象中不含有该string对象中的括号,则将该元素放入栈stk中,反之则判断Map类型中对应value是否为栈中最后一个元素,如果是,则从栈stk中取出,如果不是则return false,最后如果栈stk的长度大于0,则代表括号没有一一对应,返回false,反之则返回true
个人解答
1 | /** |
1.以下功能主要是以移动端为主
2.使用到的
ES6在移动端中没有不兼容情况,这里我基本应用在微信端,手机浏览器的话也不用担心
3.所有功能均由原生
JavaScript实现,没有任何依赖,我一贯的做法是用最少的代码,造最高效的事情
我在做一些H5单页(活动页)的时候,像我这种最求极致加载速度,且不喜欢用第三方库的人,所以决定自己动手做一些无依赖、精简高效的东西,然后按需应用在实际项目中,同时为了比百度上搜到更好用的代码分享给大家。
这里推荐前端使用vs code这个代码编辑器,理由是在声明的时候写好标准的JSDoc注释,在调用时会有很全面的代码提示,让弱类型的javascript也有类型提示
前端必备技能,也是使用最多的功能。我个人不喜欢用axios这个东西(懒得去看文档,而且觉得很鸡肋),几乎所有的web项目都是用的这个轮子。
第一种:fetch
1 | /** |
特别说明一下:我在H5单页的一些简单GET请求时通常用得最多,因为代码极少,就像下面这样
1 | fetch("http://xxx.com/api/get").then(response => response.text()).then(res => { |
第二种:XMLHttpRequest,需要Promise用法在外面包多一层function做二次封装即可
1 | /** |
这是我写的第一个web功能组件,拖拽、回弹物理效果是参照开源项目Swiper.js做的,效果功能保持一致,代码实现均由自己完成
1 | /** |
非传统实现方式,性能最优
1 | /** |
在vue中使用指令去使用
1 | import Vue from "vue"; |
这个超简单,没啥好说的
1 | <!-- 先准备好一个input标签,然后设置type="file",最后挂载一个onchange事件 --> |
1 | /** |
拖拽效果参考上面swiper的实现方式,下拉中的效果是可以自己定义的
1 | // 这里我做的不是用 window 的滚动事件,而是用最外层的绑定触摸下拉事件去实现 |
就几行代码的一个方法,另外监听元素滚动到底部可以参考代码笔记
1 | /** |
这里需要说明一下应用场景:我先前做H5活动页(红包雨)的时候遇到一个问题,就是在移动端快速点击节点并播放音频的时候,aduio标签播放的速度会有很严重的延迟。后来搜了下相关资料发现一个音频API:new AudioContext,和我之前做小游戏时用到的引擎(cocos creator)音频API是一样的。然后找了挺久发现这个API的使用资料、教程还是挺少的可能是除了做H5游戏引擎的人会用到吧,比较详细的也只有MDN官网,剩下的就是一些基于这个API的JavaScript库,但是我需要用到的功能比较简单,就是点击播放无延迟。所以自己去实现一个基于new AudioContext常用的音频组件。
1 | /** |
1 | window.addEventListener("error", e => { |
我在翻 Clipboard.js 这个插件库源码的时候找到核心代码 setSelectionRange(start: number, end: number),百度上搜到的复制功能全部都少了这个操作,所以搜到的复制文本代码在 ios 和 IE 等一些浏览器上复制不了。
1 | /** |
可检测所有类型
1 | /** |
1 | /** |
1 | /** |
css适配rem750是设计稿的宽度:之后的单位直接1:1使用设计稿的大小,单位是rem
1 | html{ font-size: calc(100vw / 750); } |
1 | /** |
这里使用百度定位,无论代码封装、调用方式还是位置准确性都比微信sdk那个好用太多了,包括在任何网页端;
1 | /** |
<input type="text">使用场景:用户在输入框输入内容时,实时过滤保持数字值显示;
tips:在Firefox中设置 <input type="number"> 会有样式 bug
1 | /** |
用户在访问一个Web网站(页面)或应用时,总是希望它的加载速度快,功能流畅。如果过于慢,用户就很有可能失去耐心而离开你的Web网站或应用。作为开发人员,给自己应用提供更快的访问速度,提供很好的用户体验是必备的基础技能,而且Web开发者在开发中也可以做很多事情来改善用户体验。那我们今天就来和大家聊聊,在CSS方面有哪些技巧可以帮助我们来提高Web页面的渲染速度。
一般来说,大多数Web应用都有复杂的UI元素,而且有的内容会在设备可视区域之外(内容超出了用户浏览器可视区域),比如下图中红色区域就在手机设备屏幕可视区域之外:
在这种场合下,我们可以使用CSS的content-visibility来跳过屏幕外的内容渲染。也就是说,如果你有大量的离屏内容(Off-screen Content),这将会大幅减少页面渲染时间。
这个功能是CSS新增的特性,隶属于 W3C 的 CSS Containment Module Level 2 模块。也是对提高渲染性能影响最大的功能之一。content-visibility可以接受visible、auto和hidden三个属性值,但我们可以在一个元素上使用content-visibility:auto来直接的提升页面的渲染性能。
假设我们有一个像下面的页面,整个页面有个卡片列表,大约有375张,大约在屏幕可视区域能显示12张卡片。正如下图所示,渲染这个页面浏览器用时大约1037ms:
你可以给所有卡片添加content-visibility:
1 | .card { |
所有卡片加入content-visibility样式之后,页面的渲染时间下降到150ms,差不多提高了六倍的渲染性能:
正如你所看到的,content-visibility非常强大,提高页面渲染非常有用。换然话说,有了CSS的content-visibility属性,影响浏览器的渲染过程就变得更加容易。本质上,这个属性 改变了一个元素的可见性,并管理其渲染状态。
content-visibility有点类似于CSS的display和visibility属性,然而,content-visibility的操作方式与这些属性不同。
content-visibility的关键能力是,它允许我们推迟我们选择的HTML元素渲染。 默认情况之下,浏览器会渲染DOM树内所有可以被用户查看的元素。用户可以看到视窗可视区域中所有元素,并通过滚动查看页面内其他元素。一次渲染所有的元素(包括视窗可视区域之外不可见的HTML元素)可以让浏览器正确计算页面的尺寸,同时保持整个页面的布局和滚动条的一致性。
如果浏览器不渲染页面内的一些元素,滚动将是一场噩梦,因为无法正确计算页面高度。这是因为,content-visibility会将分配给它的元素的高度(height)视为0,浏览器在渲染之前会将这个元素的高度变为0,从而使我们的页面高度和滚动变得混乱。但如果已经为元素或其子元素显式设置了高度,那这种行为就会被覆盖。如果你的元素中没显式设置高度,并且因为显式设置height可能会带来一定的副作用而没设置,那么我们可以使用contain-intrinsic-size来确保元素的正确渲染,同时也保留延迟渲染的好处。
1 | .card { |
这也意味着它将像有一个“固有尺寸”(Intrinsic-size)的单一子元素一样布局,确保你没设置尺寸的div(示例中的.card)仍然占据空间。contain-intrinsic-size作为一个占位符尺寸来替代渲染内容。
虽然contain-intrinsic-size能让元素有一个占位空间,但如果有大量的元素都设置了content-visibility: auto,滚动条仍然会有较小的问题。
content-visibility提供的另外两个值visible和hidden可以让我们实现像元素的显式和隐藏,类似于display的none和非none值的切换:
在这种情况下,content-visibility可以提高频繁显示或隐藏的元素的渲染性能,例如模态框的显示和隐藏。content-visibility可以提供这种性能提升,这要归功于其隐藏值(hidden)的功能与其他值的不同:
display: none:隐藏元素并破坏其渲染状态。 这意味着取消隐藏元素与渲染具有相同内容的新元素一样昂贵visibility: hidden:隐藏元素并保持其渲染状态。 这并不能真正从文档中删除该元素,因为它(及其子树)仍占据页面上的几何空间,并且仍然可以单击。 它也可以在需要时随时更新渲染状态,即使隐藏也是如此content-visibility: hidden:隐藏元素并保留其渲染状态。这意味着该元素隐藏时行为和display: none一样,但再次显示它的成本要低得多content-visibility属性的扩展阅读:
content-visibility: the new CSS property that boosts your rendering performancecontent-visibilityCSS渲染器(CSS Renderer)在渲染CSS样式之前需要一个准备过程,因为有些CSS属性需要CSS渲染器事先做很多准备才能实现渲染。这就很容易导致页面出现卡顿,给用户带来不好的体验。
比如Web上的动效,通常情况之下,Web动画(在动的元素)是和其他元素一起定期渲染的,以往在动画开发时,会使用CSS的3D变换(transform中的translate3d()或translateZ())来开启GPU加速,让动画变得更流畅,但这样做是一种黑魔法,会将元素和它的上下文提到另一个“层”,独立于其他元素被渲染。可这种将元素提取到一个新层,相对来说代价也是昂贵的,这可能会使transform动画延迟几百毫秒。
不过,现在我可以不使用transform这样的Hack手段来开启GPU加速,可以直接使用CSS的will-change属性,该属性可以表明元素将修改特定的属性,让浏览器事先进行必要的优化。也就是说,will-change是一个UA提示,它不会对你使用它的元素产生任何样式上的影响。但值得注意的是,如果创建了新的层叠上下文,它可以产生外观效果。
比如下面这样的一个动画示例:
1 | <!-- HTML --> |
浏览器渲染上面的代码时,浏览器将为该元素创建一个单独的层。之后,它将该元素的渲染与其他优化一起委托给GPU,即,浏览器会识别will-change属性,并优化未来与不透明相关的变化。这将使动画变得更加流畅,因为GPU加速接管了动画的渲染。
根据 @Maximillian Laumeister 所做的性能基准,可以看到,他通过这种单行变化获得了超过
120FPS的渲染速度,和最初的渲染速度(大约50FPS)相比,提高70FPS左右。
will-change的使用并不复杂,它能接受的值有:
auto:默认值,浏览器会根据具体情况,自行进行优化scroll-position:表示开发者将要改变元素的滚动位置,比如浏览器通常仅渲染可滚动元素“滚动窗口”中的内容。而某些内容超过该窗口(不在浏览器的可视区域内)。如果will-change显式设置了该值,将扩展渲染“滚动窗口”周围的内容,从而顺利地进行更长,更快的滚动(让元素的滚动更流畅)content:表示开发者将要改变元素的内容,比如浏览器常将大部分不经常改变的元素缓存下来。但如果一个元素的内容不断发生改变,那么产生和维护这个缓存就是在浪费时间。如果will-change显式设置了该值,可以减少浏览器对元素的缓存,或者完全避免缓存。变为从始至终都重新渲染元素。使用该值时需要尽量在文档树最末尾上使用,因为该值会被应用到它所声明元素的子节点,要是在文档树较高的节点上使用的话,可能会对页面性能造成较大的影响<custom-ident>:表示开发者将要改变的元素属性。如果给定的值是缩写,则默认被扩展全,比如,will-change设置的值是padding,那么会补全所有padding的属性,如 will-change: padding-top, padding-right, padding-bottom, padding-left;详细的使用,请参阅:
will-change Propertywill-change虽然说will-change能提高性能,但这个属性应该被认为是最后的手段,它不是为了过早的优化。只有消退你必须处理性能问题时,你才应该使用它。如果你滥用的话,反而会降低Web的性能。比如:
使用
will-change表示该元素在未来会发生变化。
因此,如果你试图将will-change和动画同时使用,它将不会给你带来优化。因此,建议在父元素上使用will-change,在子元素上使用动画。
1 | .animate-element-parent { |
不要使用非动画元素。
当你在一个元素上使用will-change时,浏览器会尝试通过将元素移动到一个新的图层并将转换工作交互GPU来优化它。如果你没有任何要转换的内容,则会导致资源浪费。
除此之外,要用好will-change也不是件易事,MDN在这方面做出了相应的描述:
will-change 应用到太多元素上:浏览器已经尽力尝试去优化一切可以优化的东西了。有一些更强力的优化,如果与 will-change 结合在一起的话,有可能会消耗很多机器资源,如果过度使用的话,可能导致页面响应缓慢或者消耗非常多的资源。比如 *{will-change: transform, opacity;}will-change 属性,则表示目标元素可能会经常变化,浏览器会将优化工作保存得比之前更久。所以最佳实践是当元素变化之前和之后通过脚本来切换 will-change 的值will-change 优化:如果你的页面在性能方面没什么问题,则不要添加 will-change 属性来榨取一丁点的速度。 will-change 的设计初衷是作为最后的优化手段,用来尝试解决现有的性能问题。它不应该被用来预防性能问题。过度使用 will-change 会导致大量的内存占用,并会导致更复杂的渲染过程,因为浏览器会试图准备可能存在的变化过程。这会导致更严重的性能问题。will-change 属性。最后需要注意的是,建议在完成所有动画后,将元素的will-change删除。下面这个示例展示如何使用脚本正确地应用 will-change 属性的示例,在大部分的场景中,你都应该这样做。
1 | var el = document.getElementById('element'); |
在实际使用will-change可以记作以下几个规则,即 五可做,三不可做:
will-changewill-change足够的时间令其发挥该有的作用<custom-ident>来针对超特定的变化(如,left, opacity等)will-changeW3C的CSS Containment Module Level 2除了提供前面介绍的content-visibility属性之外,还有另一个属性contain。该属性允许我们指定特定的DOM元素和它的子元素,让它们能够独立于整个DOM树结构之外。目的是能够让浏览器有能力只对部分元素进行重绘、重排,而不必每次针对整个页面。即,允许浏览器针对DOM的有限区域而不是整个页面重新计算布局,样式,绘画,大小或它们的任意组合。
在实际使用的时候,我们可以通过contain设置下面五个值中的某一个来规定元素以何种方式独立于文档树:
layout :该值表示元素的内部布局不受外部的任何影响,同时该元素以及其内容也不会影响以上级paint :该值表示元素的子级不能在该元素的范围外显示,该元素不会有任何内容溢出(或者即使溢出了,也不会被显示)size :该值表示元素盒子的大小是独立于其内容,也就是说在计算该元素盒子大小的时候是会忽略其子元素content :该值是contain: layout paint的简写strict :该值是contain: layout paint size的简写在上述这几个值中,size、layout和paint可以单独使用,也可以相互组合使用;另外content和strict是组合值,即content是layout paint的组合,strict是layout paint size的组合。
contain的size、layout和paint提供了不同的方式来影响浏览器渲染计算:
size: 告诉浏览器,当其内容发生变化时,该容器不应导致页面上的位置移动layout:告诉浏览器,容器的后代不应该导致其容器外元素的布局改变,反之亦然paint:告诉浏览器,容器的内容将永远不会绘制超出容器的尺寸,如果容器是模糊的,那么就根本不会绘制内容@Manuel Rego Casasnovas提供了一个示例,向大家阐述和演示了contain是如何提高Web页面渲染性能。这个示例中,有10000个像下面这样的DOM元素:
1 | <div class="item"> |
使用JavaScript的textContent这个API来动态更改div.item > div的内容:
1 | const NUM_ITEMS = 10000; |
如果不使用contain,即使更改是在单个元素上,浏览器在布局上的渲染也会花费大量的时间,因为它会遍历整个DOM树(在本例中,DOM树很大,因为它有10000个DOM元素):
在本例中,div的大小是固定的,我们在内部div中更改的内容不会溢出它。因此,我们可以将contain: strict应用到项目上,这样当项目内部发生变化时,浏览器就不需要访问其他节点,它可以停止检查该元素上的内容,并避免到外部去。
尽管这个例子中的每一项都很简单,但通过使用contain,Web性能得到很大的改变,从~4ms降到了~0.04ms,这是一个巨大的差异。想象一下,如果DOM树具有非常复杂的结构和内容,但只修改了页面的一小部分,如果可以将其与页面的其他部分隔离开来,那么将会发生什么情况呢?
有关于contain的更多内容:
contain Property在Web开发的过程中,难免会使用@font-face技术引用一些特殊字体(系统没有的字体),同时也可能会配合变量字体特性,使用更具个性化的字体。
使用@font-face加载字体策略大概如下图所示:
上图来自于@zachleat的《A COMPREHENSIVE GUIDE TO FONT LOADING STRATEGIES》一文。
Web中使用非系统字体(@font-face规则引入的字体)时,浏览器可能没有及时得到Web字体,就会让它用后备系统字体渲染,然后优化我们的字体。这个时候很容易引起未编排(Unstyled)的文本引起闪烁,整个排版本布局也看上去会偏移一下(FOUT)。
幸运的是,根据@font-face规则,font-display属性定义了浏览器如何加载和显示字体文件,允许文本在字体加载或加载失败时显示回退字体。可以通过依靠折中无样式文本闪现使文本可见替代白屏来提高性能。
CSS的font-display属性有五个不同的值:
auto :默认值。典型的浏览器字体加载的行为会发生,也就是使用自定义字体的文本会先被隐藏,直到字体加载结束才会显示。即字体展示策略与浏览器一致,当前,大多数浏览器的默认策略类似blockblock :给予字体一个较短的阻塞时间(大多数情况下推荐使用 3s)和无限大的交换时间。换言之,如果字体未加载完成,浏览器将首先绘制“隐形”文本;一旦字体加载完成,立即切换字体。为此,浏览器将创建一个匿名字体,其类型与所选字体相似,但所有字形都不含“墨水”。使用特定字体渲染文本之后页面方才可用,只有这种情况下才应该使用 block。swap :使用 swap,则阻塞阶段时间为 0,交换阶段时间无限大。也就是说,如果字体没有完成加载,浏览器会立即绘制文字,一旦字体加载成功,立即切换字体。与 block 类似,如果使用特定字体渲染文本对页面很重要,且使用其他字体渲染仍将显示正确的信息,才应使用 swap。fallback :这个可以说是auto和swap的一种折中方式。需要使用自定义字体渲染的文本会在较短的时间不可见,如果自定义字体还没有加载结束,那么就先加载无样式的文本。一旦自定义字体加载结束,那么文本就会被正确赋予样式。使用 fallback时,阻塞阶段时间将非常小(多数情况下推荐小于 100ms),交换阶段也比较短(多数情况下建议使用 3s)。换言之,如果字体没有加载,则首先会使用后备字体渲染。一旦加载成功,就会切换字体。但如果等待时间过久,则页面将一直使用后备字体。如果希望用户尽快开始阅读,而且不因新字体的载入导致文本样式发生变动而干扰用户体验,fallback 是一个很好的选择。optional :效果和fallback几乎一样,都是先在极短的时间内文本不可见,然后再加载无样式的文本。不过optional选项可以让浏览器自由决定是否使用自定义字体,而这个决定很大程度上取决于浏览器的连接速度。如果速度很慢,那你的自定义字体可能就不会被使用。使用 optional 时,阻塞阶段时间会非常小(多数情况下建议低于 100ms),交换阶段时间为 0。下面是使用swap值的一个例子:
1 | @font-face { |
在这个例子里我们通过只使用WOFF2文件来缩写字体。另外我们使用了swap作为font-display的值,页面的加载情况将如下图所示:
注意,
font-display一般放在@font-face规则中使用。
有关于字体加载和font-display更多的介绍,可以阅读:
@font-face source order when used with preload早前在滚动的特性和改变用户体验的滚动新特性中向大家介绍了几个可以用来改变用户体验的滚动特性,比如滚动捕捉、overscroll-behavior和scroll-behavior。
scroll-behavior是CSSOM View Module提供的一个新特性,可以轻易的帮助我们实现丝滑般的滚动效果。该属性可以为一个滚动框指定滚动行为,其他任何的滚动,例如那些由于用户行为而产生的滚动,不受这个属性的影响。
scroll-behavior接受两个值:
auto :滚动框立即滚动smooth :滚动框通过一个用户代理定义的时间段使用定义的时间函数来实现平稳的滚动,用户代理平台应遵循约定,如果有的话除此之外,其还有三个全局的值:inherit、initial和unset。
使用起来很简单,只需要这个元素上使用scroll-behavior:smooth。因此,很多时候为了让页面滚动更平滑,建议在html中直接这样设置一个样式:
1 | html { |
口说无凭,来看个效果对比,你会有更好的感觉:
有关于scroll-behavior属性更多的介绍可以再花点时间阅读下面这些文章:
scroll-behaviorscroll-behavior浏览器针对处理CSS动画和不会很好地触发重排(因此也导致绘)的动画属性进行了优化。为了提高性能,可以将被动画化的节点从主线程移到GPU上。将导致合成的属性包括 3D transforms (transform: translateZ(), rotate3d(),等),animating, transform 和 opacity, position: fixed,will-change,和 filter。一些元素,例如 <video>, <canvas> 和 <iframe>,也位于各自的图层上。 将元素提升为图层(也称为合成)时,动画转换属性将在GPU中完成,从而改善性能,尤其是在移动设备上。
今天,许多Web应用必须满足多种形式的需求,包括PC、平板电脑和手机等。为了完成这种响应式的特性,我们必须根据媒体尺寸编写新的样式。当涉及页面渲染时,它无法启动渲染阶段,直到 CSS对象模型(CSSOM)已准备就绪。根据你的Web应用,你可能会有一个大的样式表来满足所有设备的形式因素。
但是,假设我们根据表单因素将其拆分为多个样式表。在这种情况下,我们可以只让主CSS文件阻塞关键路径,并以高优先级下载它,而让其他样式表以低优先级方式下载。
1 | <link rel="stylesheet" href="styles.css"> |
将其分解为多个样式表后:
1 | <!-- style.css contains only the minimal styles needed for the page rendering --> |
默认情况下,浏览器假设每个指定的样式表都是阻塞渲染的。通过添加 media属性附加媒体查询,告诉浏览器何时应用样式表。当浏览器看到一个它知道只会用于特定场景的样式表时,它仍会下载样式,但不会阻塞渲染。通过将 CSS 分成多个文件,主要的 阻塞渲染 文件(本例中为 styles.css)的大小变得更小,从而减少了渲染被阻塞的时间。
通过 @import,我们可以在另一个样式表中包含一个样式表。当我们在处理一个大型项目时,使用 @import 可以使代码更加简洁。
关于 @import 的关键事实是,它是一个阻塞调用,因为它必须通过网络请求来获取文件,解析文件,并将其包含在样式表中。如果我们在样式表中嵌套了 @import,就会妨碍渲染性能。
1 | /* style.css */ |
与使用 @import 相比,我们可以通过多个 link 来实现同样的功能,但性能要好得多,因为它允许我们并行加载样式表。
CSS自定义属性又名CSS变量,该特性已经是非常成熟的特性了,可以在Web的开发中大胆的使用该特性:
1 | :root { --color: red; } |
在使用CSS自定义属性时,时常在root(根元素)上注册自定义属性,这种方式注册的自定义属性是个全局的自定义属性(全局变量),可以被所有嵌套的子元素继承。就上例而言,--color属性允许任何button样式将其作为变量使用。
熟悉CSS自定义属性的同学都知道,可以使用style.setProperty来重新设置已注册好的自定义属性的值。但在修改根自定义属性时,需要注意,因为它会影响Web的性能。早在2017年@Lisi Linhart 在《Performance of CSS Variables》中阐述过。
在使用CSS变量时,我们总是要注意我们的变量是在哪个范围内定义的,如果改变它,将影响许多子代,从而产生大量的样式重新计算。
结合CSS变量使用calc()是一个很好的方法,可以获得更多的灵活性,限制我们需要定义的变量数量。在不同的浏览器中测试calc()与CSS变量的结合,并没有发现任何大的性能问题。然而在一些浏览器中对一些单位的支持还是有限的,比如deg或ms,所以我们必须记住这一点。
如果我们比较一下在JavaScript中通过内联样式设置变量与setProperty方法的性能标志,浏览器之间有一些明显的差异。在Safari中通过内联样式设置属性的速度非常快,而在Firefox中则非常慢,所以使用setProperty设置变量是首选
有关于这方面的具体细节就不在这阐述了,如果你对这方面感兴趣的话,可以阅读下面这几篇文章:
可能很多人会说,5G已到来,终端设备性能越来越好,网络环境也越来越强,Web性能已不是问题了,但事实上在Web开发过程中总是难免碰到性能是的问题。而且我们为用户提供更流畅的体验也是我们必备技术之一。时至今日,优化Web性能的方式和手段很多,但在开发时注重每个细节,可以让我们把性能做得更好。正如文章中提到这些。
除了文章提到的这几个点,还有一些其他的方法可以使用CSS来提高网页的性能。当然,文章中提到的一些特性还没有得到所有浏览器支持,比如content-visibility、contain等,但在未来它们肯定能让页面渲染带来更快的渲染。另外,文章中提到的一些技巧并没有深入阐述,比如CSS的引用方式,CSS的阻塞等。
函数节流和函数防抖,两者都是优化高频率执行js代码的一种手段。
函数防抖: 频繁触发的情况下,只有等待足够的空闲时间,才可以执行代码一次。
函数防抖的影响:防止函数在极短的时间内反复调用,造成资源的浪费
就比如在页面上的某些事件触发频率非常高,比如滚动条滚动、窗口尺寸变化、鼠标移动等,如果我们需要注册这类事件,不得不考虑效率问题,又特别是事件处理中涉及到了大量的操作,让我们为之头痛。 当窗口尺寸发生变化时,哪怕只变化了一点点,都有可能造成成百上千次对处理函数的调用, 这对网页性能的影响是极其巨大的,很可能就造成页面的阻塞。于是,我们可以考虑,每次窗口尺寸变化、滚动条滚动、鼠标移动,不要立即执行相关操作,而是等一段时间,以窗口尺寸停止变化、滚动条不再滚动、鼠标不再移动为计时起点,一段时间后再去执行操作,就像电梯关门那样。
也就是说触发高频事件 N 秒后只会执行一次,如果 N 秒内事件再次触发,则会重新计时,重新开始计时。
1 | // 简单版本 |
鼠标一直移动(一直在触发onmousemove里面的事件,但是有debounce约束),则不输出,若停止移动,则在间隔1S后输出一次(唯一的一次哦!!!))
1 | setInterval(debounce(fn,500),1000) // 第一次在1500ms后触发打印,之后每1000ms触发一次 |
应用场景有表单的防止重复提交,搜索框提示发送多次HTTP请求 有兴趣的可以输入的了解一下哦!
节流函数: 让一个函数无法在短时间内连续调用,只有当上一次函数执行后,过了规定的时间间隔,才能进行下一次该函数的调用。或者说你在操作的时候不会马上执行该函数,而是等你不操作的时候才会执行。 对于函数节流,有如下几个场景:
1 | function throttle(fn, gapTime) { |
每0.1s在任务队列中注册一个throttle函数,实现的一个简单的函数节流,结果却是一秒打出一次boom
小结::函数防抖和节流,都是控制事件触发频率的方法。
背景需求:ERP系统需增加 ”按钮权限控制“ 功能,对权限的控制粒度要普及到按钮层级。
按钮权限控制的交互方式无非两种:**”不可见”** 和 **”可见不可点”**。
不可见的交互方式相对简单,我们可使用 v-if 控制其是否显示。使用 v-show 也行,但不够保险,毕竟 v-show 只是把样式改成 display: none,在真实的 DOM 渲染还是存在的,所以更推荐 v-if 来控制不可见。
“看是能看了,但你不行了”。
cursor: not-allowed ,置灰之类的云云;preventDefault/stopProgration 可实现;最终产品需求选择了 “可见不可点”,原因可能就觉得不可见太简单了。(¬_¬)
@click绑定函数逐个修改,遂放弃该方案);preventDefautl/stopProgration, 感觉能用指令的方式对 DOM 元素进行事件监听,允许的话则让事件正常执行,不允许则拦截屏蔽;最终选择了指令的方式,最小成本扩展,避免改动现有业务代码逻辑。
针对权限控制需做点击劫持的元素:
具体实现方案请看下文:
1 | // 用户登陆后,获取该用户权限 CODE 码,并存储至 store |
1 | const disableClickFn = (event) => { |
addEventListener 第三个参数我们使用 useCapture 为 true 让其在捕获阶段触发,因此这里的事件监听器会优先 @click 触发回调;stopImmediatePropagation 阻止事件冒泡和其它相同事件监听器的触发;如果多个事件监听器被附加到相同元素的相同事件类型上,当此事件触发时,它们会按其被添加的顺序被调用。如果在其中一个事件监听器中执行
stopImmediatePropagation(),那么剩下的事件监听器都不会被调用。MSDN - stopImmediatePropagation
1 | .permission-disabled { |
这里使用了一个比较陌生的 CSS 属性, pointer-events。
CSS3 的 pointer-events 属性指定在什么情况下 (如果有) 某个特定的图形元素可以成为鼠标事件的 target。 更多用法参考:MSDN - pointer-events
这里使用 pointer-events 只是一个辅助功能,并不一定意味着元素上的事件监听器永远不会触发,如果后代元素有指定 pointer-events 并允许成为事件目标的话,是可以触发父元素事件,而且单纯依靠 CSS 属性来控制不点击,还是有风险,因此这里仅作辅助作用。
1 | import { getStore, } from '@/util/store'; |
1 | // 指令方式(这里的 oms/order/save 就是对应用户登陆时 CODE 权限码) |