前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何使页面交互更流畅

如何使页面交互更流畅

作者头像
牧云云
发布2019-05-26 08:44:35
9200
发布2019-05-26 08:44:35
举报
文章被收录于专栏:云瓣云瓣云瓣

流畅性

本篇是基于 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);
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-05-26 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 流畅性
  • 被动交互
  • 主动交互
    • 让用户感觉到流畅
    • 像素管道
    • 保证主动交互让用户感觉流畅
      • Web Worker
        • Time Slicing
          • conclusion
          • 保证被动交互让用户感觉流畅
            • 避免 CSS 选择器嵌套过深
              • 避免布局重排
                • 避免重绘
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档