FrankKai.github.io
FrankKai.github.io copied to clipboard
Canvas那些事儿
记录踩过的坑。
- 跨域绘制脏canvas问题
- canvas裁剪图片
- 如何使两次绘制之间的连续起来?
- 如何让两次绘制canvas之间有过渡效果?
- macOS与windows上的canvas.toDataURL()不同
- 如何解决canvas内存泄漏问题
- 跨域绘制脏canvas问题 DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.Tainted canvases may not be exported.
canvas.toDataURL('image/jpeg');
绘制异域图片到canvas画布导致矩阵变脏,从而转化base64格式失败,是一个canvas跨域问题。
export class CanvasImage {
constructor(url, width, height) {
this._url = url;
this._element = new Image(width, height);
this._setImgCrossOrigin(); //必须在获取异域资源前允许img跨域,也就是为img标签设置corssOrigin属性。
this._setImgSrc();
}
_setImgSrc() {
this._element.src = this._url;
}
_setImgCrossOrigin() {
this._element.setAttribute('crossOrigin', 'Anonymous');
}
}
canvas裁剪图片
思路:根据mousedown起点,mouseup终点计算偏移,重新绘制一次canvas。
<template>
<div>
<canvas id="canvas" style="border: 1px solid #ccc" width="260" height="208"></canvas>
<div>
<img id="source" src="http://ov6jc8fwp.bkt.clouddn.com/[email protected]">
</div>
</div>
</template>
<script>
export default {
name: 'index',
data() {
return {
dWidth: 260,
dHeight: '',
downY: 0,
upY: 0,
offset: 0,
};
},
mounted() {
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const image = document.getElementById('source');
this.ctx = ctx;
this.image = image;
image.onload = () => {
const rate = parseFloat((image.width / 260).toFixed(10));
this.dHeight = image.height / rate;
ctx.drawImage(image, 0, 0, this.dWidth, this.dHeight);
};
canvas.onmousedown = (e) => {
console.log('帅哥按下了鼠标:', 'x:', e.offsetX, 'y:', e.offsetY);
this.downY = e.offsetY;
};
canvas.onmouseup = (e) => {
console.log('帅哥松开了鼠标:', 'y:', e.offsetY);
this.upY = e.offsetY;
this.offset = this.offset + (this.upY - this.downY);
this.render(this.offset);
};
},
methods: {
render(offset) {
this.ctx.clearRect(0, 0, 260, 208);
this.ctx.drawImage(this.image, 0, offset, this.dWidth, this.dHeight);
},
},
};
</script>
<style scoped>
</style>
如何使两次绘制之间的连续起来?
思路:通过记录上一次的offsetY值,上一次的offset第一次是mousedown的offsetY值,之后是mousemove的offsetY值,持续重绘canvas,达到连续效果。
<template>
<div>
<canvas id="canvas" style="border: 1px solid #ccc" width="260" height="208"></canvas>
<div>
<img id="source" src="http://ov6jc8fwp.bkt.clouddn.com/2440018536-58e12656c3533_huge256.jpg">
</div>
</div>
</template>
<script>
export default {
name: 'index',
data() {
return {
dWidth: 260,
dHeight: '',
downY: 0,
offset: 0,
};
},
mounted() {
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const image = document.getElementById('source');
this.ctx = ctx;
this.image = image;
image.onload = () => {
const rate = parseFloat((image.width / 260).toFixed(10));
this.dHeight = parseFloat(image.height / rate).toFixed(10);
ctx.drawImage(image, 0, 0, this.dWidth, this.dHeight);
};
// begin
let lastOffsetY;
const handleMousemove = (e) => {
const currentOffsetY = e.offsetY;
this.offset = this.offset + (currentOffsetY - lastOffsetY);
const topBorder = 0;
const bottomBorder = -this.dHeight + 208;
if (this.offset < topBorder && this.offset > bottomBorder) {
this.render(this.offset);
} else if (this.offset < bottomBorder) {
this.offset = bottomBorder;
} else if (this.offset > topBorder) {
this.offset = topBorder;
}
lastOffsetY = e.offsetY;
};
canvas.addEventListener('mousedown', (e) => {
console.log('帅哥按下了鼠标:', 'x:', e.offsetX, 'y:', e.offsetY);
lastOffsetY = e.offsetY;
this.downY = e.offsetY;
canvas.addEventListener('mousemove', handleMousemove);
});
// end
canvas.addEventListener('mouseup', (e) => {
console.log('帅哥松开了鼠标:', 'y:', e.offsetY);
canvas.removeEventListener('mousemove', handleMousemove);
});
canvas.addEventListener('mouseleave', (e) => {
console.log('帅哥离开了画布:', 'y:', e.offsetY);
canvas.removeEventListener('mousemove', handleMousemove);
});
},
methods: {
render(offset) {
this.ctx.clearRect(0, 0, 260, 208);
this.ctx.drawImage(this.image, 0, offset, this.dWidth, this.dHeight);
},
},
};
</script>
<style scoped>
</style>
如何让两次绘制canvas之间有过渡效果?
思路:根据mousemove的offsetY与mousedown的offsetY做比较,给其一个固定的偏移量(我们这里是4),不断重新绘制canvas,达到过渡效果。
<template>
<div>
<canvas id="canvas" style="border: 1px solid #ccc" width="260" height="208"></canvas>
<div>
<img id="source" src="http://ov6jc8fwp.bkt.clouddn.com/[email protected]">
</div>
</div>
</template>
<script>
export default {
name: 'index',
data() {
return {
dWidth: 260,
dHeight: '',
downY: 0,
moveY: 0,
offset: 0,
};
},
mounted() {
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const image = document.getElementById('source');
this.ctx = ctx;
this.image = image;
image.onload = () => {
const rate = parseFloat((image.width / 260).toFixed(10));
this.dHeight = parseFloat(image.height / rate).toFixed(10);
ctx.drawImage(image, 0, 0, this.dWidth, this.dHeight);
};
// begin
const handleMousemove = (e) => {
this.moveY = e.offsetY;
if (this.moveY - this.downY > 0) {
this.offset = this.offset + 4;
} else {
this.offset = this.offset - 4;
}
if (this.offset < 0 && this.offset > -this.dHeight + 208) {
this.render(this.offset);
}
};
// end
canvas.addEventListener('mousedown', (e) => {
console.log('帅哥按下了鼠标:', 'x:', e.offsetX, 'y:', e.offsetY);
this.downY = e.offsetY;
canvas.addEventListener('mousemove', handleMousemove);
});
canvas.addEventListener('mouseup', (e) => {
console.log('帅哥松开了鼠标:', 'y:', e.offsetY);
canvas.removeEventListener('mousemove', handleMousemove);
});
canvas.addEventListener('mouseleave', (e) => {
console.log('帅哥离开了画布:', 'y:', e.offsetY);
canvas.removeEventListener('mousemove', handleMousemove);
});
},
methods: {
render(offset) {
this.ctx.clearRect(0, 0, 260, 208);
this.ctx.drawImage(this.image, 0, offset, this.dWidth, this.dHeight);
},
},
};
</script>
<style scoped>
</style>
macOS与windows上的canvas.toDataURL()
不同
场景:合成图片后,通过canvas.toDataURL()将合成canvas转化为base64格式,然后通过七牛SDK上传七牛。
影响:windows失真严重。通过macOS与windows系统上传的图片清晰度不同,假设windows为255KB,macOS为789KB,清晰度macOS约为windows的3倍。
原因:canvas.toDataURL()
的生成结果不同,windows 255KB的为347534,macOS 789KB的为1088150。
根本原因:屏幕分辨率不同,无解。
如何解决canvas内存泄漏问题
最近遇到一个canvas内存泄漏问题,原因是没有将释放canvas的2D上下文内存导致的。
内存泄漏
import React, { useRef } from 'react'
const canvasComponent = () => {
const canvasRef = useRef(null)
const canvasContext = useRef(null)
useEffect(()=>{
canvasContext.current = canvasRef.current.getContext('2d')
}, [])
const codeStart = () => {
// ...
}
const codeEnd = () => {
// ...
}
return <canvas ref={canvasRef} style={{ display: 'none' }} />
}
export default canvasComponent;
修复内存泄漏
import React, { useRef } from 'react'
const canvasComponent = () => {
const canvasRef = useRef(null)
const canvasContext = useRef(null)
useEffect(()=>{
canvasContext.current = canvasRef.current.getContext('2d')
}, [])
const codeStart = () => {
canvasContext.current = canvasRef.current.getContext('2d') // 获取新的canvas的2D上下文
// ...
}
const releaseMemory = () =>{
canvasContext.current = null; // 释放canvas的2D上下文内存
}
const codeEnd = () => {
// ...
releaseMemory();
}
return <canvas ref={canvasRef} style={{ display: 'none' }} />
}
export default canvasComponent;