工作场景中,可能会遇到一下场景
- 用户分享使用canvas生成图片
- 用户提交过往数据,生成pdf等文件,比如生成几十兆的文件
以上场景,第一种还好,直接canvas绘制即可,基本上是零延迟,有延迟也是在可以接受的范围内,但如果对资源进行高斯模糊,就会出现用户有效反馈时间变长(TTI),表现为点击了事件却很久才有响应
但对于第二种,若是放在客户端阶段生成怕是要炸掉,一是等待时间长不说,二是因为客户端的不确定性怕是要炸掉,比如说对于低端机型内存不够直接闪退等
如何解决
一:使用webworker,解决大量计算占用线程,交互事件无法及时执行的问题。
基础知识可参见阮一峰老师的Web Worker 使用教程
兼容性如下表,对所有的内核机型都是适用的
既然canvas绘制了占用了主线程,那我们就把他放到另一个线程去执行。以Vue为例,让页面一进来就去执行高斯模糊操作。
关键代码如下
main.js
// 使用tti-polyfill包来反映Time to Interactive
import Vue from 'vue'
import App from './App.vue'
import ttiPolyfill from 'tti-polyfill';
ttiPolyfill.getFirstConsistentlyInteractive().then((tti) => {
console.log(tti)
})
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
App.vue
<template>
<div id="app">
<div class="btn" @click="doClick">点击事件</div>
<div class="img-wrapper">
<div class="left-img">
<img src="./assets/render.jpg" ref="testImg" class="test-img" alt="" />
</div>
<div class="right-img">
<!-- width="1200" height="800" -->
<!-- width="4480" height="6620" -->
<canvas width="4480" height="6620" id="_poster-canvas"></canvas>
</div>
</div>
</div>
</template>
<script>
import toGauss from './gauss'
import Worker from './gauss.worker'
export default {
name: 'App',
components: {
},
methods: {
doClick() {
console.log('我是点击事件')
}
},
mounted() {
const imgEle = this.$refs.testImg
imgEle.onload = () => {
const imgWidth = imgEle.naturalWidth
const imgHeight = imgEle.naturalHeight
const canvas = document.getElementById('_poster-canvas')
const ctx = canvas.getContext('2d')
ctx.drawImage(imgEle, 0, 0, imgWidth, imgHeight, 0, 0, imgWidth, imgHeight);
const data = ctx.getImageData(0, 0, imgWidth, imgHeight);
// const emptyData = ctx.createImageData(imgWidth, imgHeight);
// emptyData = toGauss(data)
// ctx.putImageData(emptyData,0,0)
const worker = new Worker()
worker.postMessage(data)
worker.onmessage = function (e) {
ctx.putImageData(e.data.msg, 0, 0)
}
}
}
}
</script>
<style lang="scss">
* {
margin: 0;
padding: 0;
}
.btn {
color: #ffff;
padding: 15px 30px;
font-size: 30px;
text-align: center;
background-color: black;
}
.img-wrapper {
display: flex;
width: 100vw;
overflow: hidden;
img {
width: 100%;
}
.left-img {
flex: 1;
}
.right-img {
flex: 1;
canvas {
width: 100%;
}
}
}
</style>
结果
| 属性 | 使用webworker | 没有使用webwoker |
|---|---|---|
| TTI | 1261.3000000044703ms | 5709.80000000447ms |
二:使用 selenium起一个渲染服务
什么是selenium: selenium作为一个是一个应用web应用测试工具,Selenium测试直接运行在浏览器中,就像真正的用户在操作一样。
利用这一特性,我们就可以把客户端的任务交给selenium去做。
- 下载selenium-webdriver插件
- 起一个消息队列服务,用来处理用户的提交生成的消息
const webdriver = require('selenium-webdriver')
const chrome = require('selenium-webdriver/chrome')
const io = require('selenium-webdriver/io')
const CHROMEDRIVER_EXE = process.platform === 'win32' ? 'lib/chromedriver.exe' : (process.platform === 'darwin' ? 'lib/chromedriver_mac' : 'lib/chromedriver_linux')
class Render {
constructor () {
this._driver = null
this.failedResult = { success: false, message: '生成失败'}
this.running = false
}
get driver () {
if (!this._driver) {
const service = new chrome.ServiceBuilder(io.findInPath(CHROMEDRIVER_EXE, true))
this._driver = new webdriver.Builder()
.setChromeService(service)
.forBrowser('chrome')
.build()
}
return this._driver
}
set driver (value) {
this._driver = value
}
async runTask (params, callback) {
this.running = true
const driver = this.driver
await driver.get(params.url)
await callback({ success: true, message: '生成成功'})
this.running = false
}
print (options, callback) {
return this.runTask(options, callback)
}
}
module.exports = new Render()
demo gif图
这个只是提供一个思路,当然中间还有很多case场景要考虑。
以上的demo地址:github.com/lecheng-lc/…