前端如何解决较大资源生成问题
工作场景中,可能会遇到一下场景
- 用户分享使用canvas生成图片
- 用户提交过往数据,生成pdf等文件,比如生成几十兆的文件
以上场景,第一种还好,直接canvas绘制即可,基本上是零延迟,有延迟也是在可以接受的范围内,但如果对资源进行高斯模糊,就会出现用户有效反馈时间变长(TTI),表现为点击了事件却很久才有响应
但对于第二种,若是放在客户端阶段生成怕是要炸掉,一是等待时间长不说,二是因为客户端的不确定性怕是要炸掉,比如说对于低端机型内存不够直接闪退等
如何解决
一:使用webworker,解决大量计算占用线程,交互事件无法及时执行的问题。
基础知识可参见阮一峰老师的Web Worker 使用教程
兼容性如下表,对所有的内核机型都是适用的 
既然canvas绘制了占用了主线程,那我们就把他放到另一个线程去执行。以Vue为例,让页面一进来就去执行高斯模糊操作。 关键代码如下
main.js
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
js
<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插件
- 起一个消息队列服务,用来处理用户的提交生成的消息
js
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地址:https://github.com/lecheng-lc/demo/tree/master/efficient-rendering