怎样使页面交互更流通

流通性

本篇是基于 FDCon2019 上《让你的网页更丝滑by刘博文》的复盘文。该课题也是博主感兴趣的范畴, 后续会连系 React 的 Schedule 与该文举行进一步整合, 个人博客

  • 被动交互: animation
  • 主动交互: 鼠标、键盘

被动交互

《怎样使页面交互更流通》

当前市面上的装备频次在 60 HZ 以上。

主动交互

跑以下界面 https://code.h5jun.com/pojob

连系以下代码块, 能够看到 100ms 以下的点击是顺畅的, 而凌驾 100ms 的点击就会有卡顿征象。

var observer = new PerformanceObserver(function(list) {
  var perfEntries = list.getEntries()
  console.log(perfEntries)
});
observer.observe({entryTypes: ["longtask"]});

让用户觉得到流通

权衡一个网页/App 是不是流通有个比较好用的 Rail 模子, 它也许有以下几个评判规范值。

Response —— 100ms
Animation —— 16.7ms
Idle —— 50ms
Load —— 1000ms

像素管道

像素管道平常由 5 个部份构成。JavaScript、款式、规划、绘制、合成。以下图所示:

《怎样使页面交互更流通》

衬着机能

保证主动交互让用户觉得流通

function App() {
  useEffect(() => {
    setTimeout(_ => {
      const start = performance.now()
      while (performance.now() - start < 1000) { }
      console.log('done!')
    }, 5000)
  })
  return (
    <input type="text" />
  );
}

《怎样使页面交互更流通》

平常凌驾 50 ms 认为是 long task(长使命), long task 会壅塞 main thread 的运转, 以下是两种解决方案。

Web Worker

app.js 代码以下:

import React, {useEffect} from 'react'
import WorkerCode from './worker'

function App() {
  useEffect(() => {
    const testWorker = new Worker(WorkerCode)
    setTimeout(() => {
      testWorker.postMessage({})
      testWorker.onmessage = function(ev) {
        console.log(ev.data)
      }
    }, 5000)
  })
  return (
    <input type="text" />
  );
}

worker.js 代码以下:

const workerCode = () => {
  self.onmessage = function() {
    const start = performance.now()
    while (performance.now() - start < 1000) { }
    postMessage('done!')
  }
}

《怎样使页面交互更流通》

此时在输入框输入时没有卡顿的觉得。

Time Slicing

下面是别的一种使页面流通的要领 —— Time Slicing(时候分片)。

视察 Chrome 的 Performance, 火焰图以下,

《怎样使页面交互更流通》

从火焰图能够看出主线程被拆分为了多个时候分片, 所以不会形成卡顿。时候分片的代码片断以下所示:

function timeSlicing(gen) {
  if (typeof gen === 'function') gen = gen()
  if (!gen || typeof gen.next !== 'function') return

  (function next() {
    const res = gen.next() // ①
    if (res.done) return // ⑤
    setTimeout(next) // ③
  })()
}

// 挪用时候分片函数
timeSlicing(function* () {
  const start = performance.now()
  while (performance.now() - start < 1000) {
    console.log('实行逻辑')
    yield // ②
  }
  console.log('done') // ④
})

该函数虽然代码量不长, 但却不容易明白。前置学问 Generator

下面临该函数举行剖析:

  1. 往时候分片函数 timeSlicing 中传入 generator 函数;
  2. 函数的实行递次 —— ①、②、③、① (此时有个比赛的关联, 假如 performance.now() - start < 1000 则继承 ②、③, 假如 performance.now() - start >= 1000 则跳出轮回实行 ④、⑤);

conclusion

针对 long task 会壅塞 main thread 的运转的情况, 给出两种解决方案:

  • Web Worker: 运用 Web Worker 供应的多线程环境来处置惩罚 long task;
  • Time Slicing: 将主线程上的 long task 举行时候分片;

保证被动交互让用户觉得流通

保证 16.7ms 有新的一帧传输到界面上。撤除用户的逻辑代码, 一帧内留给浏览器整合的时候也许只要 6ms 摆布, 回到像素管道上来, 我们能够从这几方面举行优化:

防止 CSS 选择器嵌套过深

Style 这部份的优化在 css 款式选择器的运用, css 选择器运用的层级越多, 消耗的时候越多。以下是测试 css 选择器差别层级挑选雷同元素的一次测试效果。

div.box:not(:empty):last-of-type span         2.25ms
index.html:85 .box--last span                 0.28ms
index.html:85 .box:nth-last-child(-n+1) span  2.51ms

防止规划重排

// 先修正值
el.style.witdh = '100px'
// 后取值
const width = el.offsetWidth

这段代码有什么问题呢?

《怎样使页面交互更流通》

能够看到它会形成规划重排。

《怎样使页面交互更流通》

应对的战略是调解它们的实行递次,

// 先取值
const width = el.offsetWidth
// 后修正值
el.style.witdh = '100px'

《怎样使页面交互更流通》

能够看到经由换取递次后, 后实行的 el.style.width 会新开一个像素管道, 而不会在本来的像素管道举行重排。

另外不要在轮回中实行以下的操纵,

for (var i = 0; i < 1000; i++) {
  const newWidth = container.offsetWidth; // ①
  boxes[i].style.width = newWidth + 'px'; // ②
}

能够在火焰图中看到它发生了重绘的正告,

《怎样使页面交互更流通》

实行递次是 ①②①②①②①…, 倘使我们在第一个 ① 背面插进去一条竖线后 ①|②①②①②①, 其就变成先修正值后取值的情形, 所以也就发生了重绘!

准确的运用姿态应当以下:

const newWidth = container.offsetWidth;
for (var i = 0; i < 1000; i++) {
  boxes[i].style.width = newWidth + 'px';
}

防止重绘

建立 Layers(图层) 能够防止重绘,

{
  transform: translateZ(0);
}
    原文作者:牧云云
    原文地址: https://segmentfault.com/a/1190000019294778
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞