一、背景
二、绘制流程
2.1 基本元素
2.1.1 加载图片
class DrawImage {
constructor(ctx, imageObj) {
this.ctx = ctx;
this.imageObj = imageObj;
}
draw() {
const {centerX, centerY, src, sx = 1, sy = 1} = this.imageObj;
const img = new Image();
img.onload = () => {
const imgWidth = img.width;
const imgHeight = img.height;
this.ctx.save();
this.ctx.scale(sx, sy);
this.ctx.drawImage(img, centerX - imgWidth * sx / 2, centerY - imgHeight * sy / 2);
this.ctx.restore();
};
img.src = src;
}
}
2.1.2 绘制文字
class DrawText {
constructor(ctx, textObj) {
this.ctx = ctx;
this.textObj = textObj;
}
draw() {
const {x, y, font, content, lineHeight = 20, width, fillStyle = '#000000', textAlign = 'start', textBaseline = 'middle'} = this.textObj;
const branchsContent = this.getBranchsContent(content, width);
this.ctx.save();
this.ctx.fillStyle = fillStyle;
this.ctx.textAlign = textAlign;
this.ctx.textBaseline = textBaseline;
this.ctx.font = font;
branchsContent.forEach((branchContent, index) => {
this.ctx.fillText(branchContent, x, y + index * lineHeight);
});
this.ctx.restore();
}
getBranchsContent(content, width) {
if (!width) {
return [content];
}
const charArr = content.split('');
const branchsContent = [];
let tempContent = '';
charArr.forEach(char => {
if (this.ctx.measureText(tempContent).width < width && this.ctx.measureText(tempContent + char).width <= width) {
tempContent += char;
}
else {
branchsContent.push(tempContent);
tempContent = '';
}
});
branchsContent.push(tempContent);
return branchsContent;
}
}
2.1.3 绘制矩形
class DrawRect {
constructor(ctx, rectObj) {
this.ctx = ctx;
this.rectObj = rectObj;
}
draw() {
const {x, y, width, height, fillStyle, lineWidth = 1} = this.rectObj;
this.ctx.save();
this.ctx.fillStyle = fillStyle;
this.ctx.lineWidth = lineWidth;
this.ctx.fillRect(x, y, width, height);
this.ctx.restore();
}
}
2.1.4 绘制圆
class DrawCircle {
constructor(ctx, circleObj) {
this.ctx = ctx;
this.circleObj = circleObj;
}
draw() {
const {x, y, R, startAngle = 0, endAngle = Math.PI * 2, lineWidth = 1, fillStyle} = this.circleObj;
this.ctx.save();
this.ctx.lineWidth = lineWidth;
this.ctx.fillStyle = fillStyle;
this.ctx.beginPath();
this.ctx.arc(x, y, R, startAngle, endAngle);
this.ctx.closePath();
this.ctx.fill();
this.ctx.restore();
}
}
2.2 AST树
2.2.1 静态部分AST树
const graphicAst = [
{
type: 'rect',
x: 0,
y: 0,
width: 1400,
height: 400,
fillStyle: '#cec9ae'
},
{
type: 'img',
centerX: 290,
centerY: 200,
sx: 0.9,
sy: 0.9,
src: 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Finews.gtimg.com%2Fnewsapp_match%2F0%2F11858683821%2F0.jpg&refer=http%3A%2F%2Finews.gtimg.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1622015341&t=cc1bd95777dfa37d88c48bb6e179778e'
},
{
type: 'text',
x: 600,
y: 60,
textAlign: 'start',
textBaseline: 'middle',
font: 'normal 40px serif',
lineHeight: 50,
width: 180,
fillStyle: '#000000',
content: '灰太狼是最好的一头狼,它每天都在梦想着吃羊,一直没有实现,但是从不气馁。'
},
{
type: 'text',
x: 600,
y: 170,
textAlign: 'start',
textBaseline: 'middle',
font: 'normal 30px serif',
lineHeight: 50,
width: 180,
fillStyle: '#7F7F7F',
content: '为灰太狼加油、为灰太狼喝彩,?'
},
{
type: 'text',
x: 1200,
y: 360,
textAlign: 'start',
textBaseline: 'ideographic',
font: 'normal 30px serif',
lineHeight: 50,
width: 180,
fillStyle: '#949494',
content: '阅读'
},
{
type: 'text',
x: 1260,
y: 363,
textAlign: 'start',
textBaseline: 'ideographic',
font: 'normal 30px serif',
lineHeight: 50,
width: 180,
fillStyle: '#949494',
content: '520'
}
];
2.2.2 动态部分AST树
function getMarqueeAst(startX, endX, count, options = {}) {
const {y = 15, R = 15} = options;
if (!(endX >= startX && count > 0)) {
return [];
}
const interval = (endX - startX) / count;
const marqueeAstArr = [];
for (let i = 0; i < count; i++) {
const RValue = Math.random() * 255;
const GValue = Math.random() * 255;
const BValue = Math.random() * 255;
const fillStyle = `rgb(${RValue}, ${GValue}, ${BValue})`;
marqueeAstArr.push({
type: 'circle',
x: startX + i * interval,
y,
R,
fillStyle
});
}
return marqueeAstArr;
}
2.3 主函数类
class Draw {
constructor(canvasDom) {
this._canvasDom = canvasDom;
this.ctx = this._canvasDom.getContext('2d');
this.width = this._canvasDom.width;
this.height = this._canvasDom.height;
}
// 绘制函数
draw(ast) {
ast.forEach(elementObj => {
this.drawFactory(elementObj);
const {children} = elementObj;
// 递归调用
if (children && Array.isArray(children)) {
this.draw(children);
}
});
}
// 工厂模型绘制对应基本元素
drawFactory(elementObj) {
const {type} = elementObj;
switch(type) {
case 'img': {
this.drawImage(elementObj);
break;
}
case 'text': {
this.drawText(elementObj);
break;
}
case 'rect': {
this.drawRect(elementObj);
break;
}
case 'circle': {
this.drawCircle(elementObj);
break;
}
}
}
drawImage(imageObj) {
const drawImage = new DrawImage(this.ctx, imageObj);
drawImage.draw();
}
drawText(textObj) {
const drawText = new DrawText(this.ctx, textObj);
drawText.draw();
}
drawRect(rectObj) {
const drawRect = new DrawRect(this.ctx, rectObj);
drawRect.draw();
}
drawCircle(circleObj) {
const drawCircle = new DrawCircle(this.ctx, circleObj);
drawCircle.draw();
}
clearCanvas() {
this.ctx.clearRect(0, 0, this.width, this.height);
}
}
2.4 内容绘制
2.4.1 静态内容绘制
const basicCanvasDom = document.getElementById('basicCanvas');
const drawBasicInstance = new Draw(basicCanvasDom);
drawBasicInstance.draw(graphicAst);
2.4.2 绘制动画跑马灯
const animationCanvasDom = document.getElementById('animationCanvas');
const drawAnimationInstance = new Draw(animationCanvasDom);
let renderCount = 0;
function animate() {
if (renderCount % 5 === 0) {
drawAnimationInstance.clearCanvas();
drawAnimationInstance.draw(getMarqueeAst(20, 1440, 22));
drawAnimationInstance.draw(getMarqueeAst(20, 1440, 22, {
y: 380
}));
}
window.requestAnimationFrame(animate);
renderCount++;
}
animate();
-
本文对应源码,关注公众号“执鸢者”,回复“canvas”获取
-
如果觉得这篇文章还不错,来个分享、点赞吧,让更多的人也看到
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!