canvas从零到一,实际案例
1.绘制路径
1.开始绘制:ctx.beginPath(); 重点,隔离产生一个域
2.描边宽度:ctx.lineWidth
3.描边颜色:ctx.strokeStyle = 'red';
4.路径开始点:ctx.moveTo(x, y);
5.路劲目标点:ctx.lineTo(x, y);
6.绘制路径:ctx.stroke();
7.关闭路径:ctx.closePath();
2.绘制矩形
1.矩形方法:ctx.rect(x, y, w, h);
2.设置填充颜色:ctx.fillStyle = '#987';
3.执行填充动作:ctx.fill();
4.绘制一个填充的矩形:ctx.fillRect(x, y, w, h); 没有描边
5.绘制一个描边矩形:ctx.strokeRect(x, y, width, height);
6.清除画布:ctx.clearRect(x, y, width, height);
3.绘制圆
ctx.beginPath();
ctx.arc(x, y, r, startAngle, endAngle(Math.PI),);
ctx.moveTo(100, 50);
ctx.lineTo(100, 100);
ctx.lineTo(150, 100);
ctx.fill();
4.绘制文字
1.设置字体大小与字体库: ctx.font = `${fontSize}px serif`;
2.设置基准线:ctx.textBaseline = 'middle';
3.绘制字体:ctx.fillText('sdddddddddd但是v', 0, 0);
4.获取文本信息: ctx.measureText(text);
5.demo:
ctx.save();
ctx.translate(0, (fontSize / 2)); // 适配安卓 ios 下的文字居中问题
ctx.fillText(text, 0, 0);
ctx.restore();
5.绘制图片
// 缩放图片,在canvas上展示
const maxW = 100;
const multiple = img.width / maxW;
ctx.drawImage(img, x, y, img.width, img.height, 0, 0, maxW, img.height / multiple);
6.制作渐变线条,矩形
const gradient = ctx.createLinearGradient(20,0, 220,0);
// Add three color stops
gradient.addColorStop(0, 'green');
gradient.addColorStop(.5, 'cyan');
gradient.addColorStop(1, 'red');
// Set the fill style and draw a rectangle
ctx.fillStyle = gradient;
7.重点方法
1.移动画板的位置:ctx.translate(x, y);
2.旋转画板:ctx.rotate(Math.PI / 180 * Angle);
3.放大缩小画板:ctx.scale(x, y);
4.保存状态:ctx.save();
5.恢复状态:ctx.restore();
6.ctx.transform(a, b, c, d, e, f);
a:水平缩放
b:垂直倾斜
c:水平偏斜
d:垂直缩放
e:水平平移
f:垂直平移
7.裁剪路径的区域:ctx.clip(path,"nonzero":非零缠绕规则,默认规则。"evenodd");
8.封装生成圆形图片的方法
/**
* @description: 生成圆角矩形
* @param {*} ctx
* @param {number} x
* @param {number} y
* @param {number} w
* @param {number} h
* @param {number} r
* @param {string} color
* @param {string} type:fill | stroke | undefined
* @return void
*/
const roundedRectangle = (ctx, x: number, y: number, w: number, h: number, r: number, color = '#000', type?: string | undefined) => {
const PI = Math.PI / 180;
ctx.translate(x, y);
ctx.save();
ctx.beginPath();
// ctx.moveTo(0, 0);
// ctx.lineTo(w, 0);
ctx.arc(w - r, r, r, PI * 270, PI * 360);
// ctx.lineTo(w, h);
ctx.arc(w - r, h - r, r, PI * 0, PI * 90);
// ctx.lineTo(0, h);
ctx.arc(r, h - r, r, PI * 90, PI * 180);
// ctx.lineTo(0, 0);
ctx.arc(r, r, r, PI * 180, PI * 270);
ctx.closePath();
ctx.fillStyle = color;
switch (type) {
case 'fill':
ctx.fill();
break;
case 'stroke':
ctx.stroke();
break;
}
ctx.restore();
};
/**
* 生成圆角图片
* @param ctx:canvas上下文
* @param img:图片实列
* @param x:坐标
* @param y:坐标
* @param w:宽
* @param h:高
* @param r:圆角
*/
interface Img {
width: number;
height: number;
}
const roundedRectangleImg = (ctx, img: Img, x: number, y: number, w: number, h: number, r = 0) => {
ctx.save();
// 缩小倍数
const multiple = img.width / w;
roundedRectangle(ctx, x, y, w, h, r);
ctx.clip();
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, w, img.height / multiple);
ctx.restore();
};
/**
* @description: 绘制文字
* @param {*} ctx
* @param {string} text
* @param {*} fontSize
* @return {*}
*/
const writeText = (ctx, text: string, fontSize = 12, x = 0, y = 0, color = '#000') => {
ctx.save();
ctx.font = `${fontSize}px serif`;
ctx.fillStyle = color;
ctx.textBaseline = 'middle';
ctx.translate(0, (fontSize / 2)); // 适配安卓 ios 下的文字居中问题
ctx.fillText(text, x, y);
ctx.measureText(text);
ctx.restore();
};
9.生成海报
注意:ios最大支持200W像素(w*h),canvas支持500w
/**
* @description: 生成圆角矩形
* @param {*} ctx
* @param {number} x
* @param {number} y
* @param {number} w
* @param {number} h
* @param {number} r
* @param {string} color
* @param {string} type:fill | stroke | undefined
* @return void
*/
const roundedRectangle = (ctx, x: number, y: number, w: number, h: number, r: number, color = '#000', type?: string | undefined) => {
const PI = Math.PI / 180;
ctx.translate(x, y);
ctx.save();
ctx.beginPath();
// ctx.moveTo(0, 0);
// ctx.lineTo(w, 0);
ctx.arc(w - r, r, r, PI * 270, PI * 360);
// ctx.lineTo(w, h);
ctx.arc(w - r, h - r, r, PI * 0, PI * 90);
// ctx.lineTo(0, h);
ctx.arc(r, h - r, r, PI * 90, PI * 180);
// ctx.lineTo(0, 0);
ctx.arc(r, r, r, PI * 180, PI * 270);
ctx.closePath();
ctx.fillStyle = color;
switch (type) {
case 'fill':
ctx.fill();
break;
case 'stroke':
ctx.stroke();
break;
}
ctx.restore();
};
/**
* 生成圆角图片
* @param ctx:canvas上下文
* @param img:图片实列
* @param x:坐标
* @param y:坐标
* @param w:宽
* @param h:高
* @param r:圆角
*/
interface Img {
width: number;
height: number;
}
const roundedRectangleImg = ({ctx, img, x, y, w, h, r = 0}) => {
ctx.save();
// 缩小倍数
const multiple = img.width / w;
roundedRectangle(ctx, x, y, w, h, r);
ctx.clip();
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, w, img.height / multiple);
ctx.restore();
};
/**
* @description: 绘制文字
* @param {*} ctx
* @param {*}canvas
* @param {string} text
* @param {*} fontSize
* @param {*} textSafeArea: 文字安全区域
* @return {*}
*/
const writeText = (ctx, canvas, text: string, x = 0, y = 0, fontSize = 12, color = '#000', textSafeArea?) => {
ctx.save();
ctx.font = `${fontSize}px serif`;
ctx.fillStyle = color;
ctx.textBaseline = 'middle';
ctx.textAlign = 'center';
ctx.translate(0, (fontSize / 2)); // 适配安卓 ios 下的文字居中问题
const textMsg: TextMetrics = ctx.measureText(text);
if (canvas.width < textMsg.width || !!textSafeArea && textSafeArea < textMsg.width) {
const newText = text.slice(0, text.length > 10 ? 10 : text.length);
ctx.fillText(newText + '...', x, y);
console.log(textMsg, 'textMsg', textMsg.width, canvas.width, text.slice(0, 10));
} else {
ctx.fillText(text, x, y);
}
ctx.restore();
};
interface CanvasCtx {
canvas: HTMLCanvasElement;
ctx: CanvasRenderingContext2D | null;
postersEle: HTMLDivElement | null;
cPx: number;
cW: number;
cH: number;
}
/**
* @description: 初始化一画布
* @param {*} canvas:canvas实例
* @param {*} id:标签id
* @param {*} w:canvas宽度
* @param {*} h:canvas高度
* @param {*} d:放大canvas倍数
* @return {} cPx:基于750计算出来的相对单位
*/
const initCanvas = ({ id, w = 0, h = 0, d = 1, canvas }): CanvasCtx => {
// 获取设备像素比
const dPR = Math.round(window.devicePixelRatio);
const postersEle: HTMLDivElement | null = document.querySelector(id);
const screenW = window.screen.width;
const screenH = window.screen.height;
// 先获取节点下是否存在pl-canvas节点
const canvasName = '#pl-canvas';
const canvasDom: HTMLCanvasElement | null = document.querySelector(canvasName);
if (!canvasDom) {
canvas = document.createElement('canvas');
canvas.setAttribute('id', 'pl-canvas');
canvas.style.width = '100%';
postersEle?.append(canvas);
}
const ctx: CanvasRenderingContext2D | null = canvas.getContext('2d');
const cW = (w || screenW) * d * dPR;
const cH = (h || screenH) * d * dPR;
canvas.width = cW;
canvas.height = cH;
const cPx = ((750 / 100) / (cW / 100) / d) * d;
return {
canvas,
ctx,
postersEle,
cPx,
cW,
cH,
};
};
/**
* @description: 初始化一张图片实例
* @param {*} async
* @return {*}
*/
const loadingPictures = async (url: string): Promise => {
return new Promise((resolve, reject) => {
const img: HTMLImageElement = new Image();
img.src = url;
img.onload = () => {
resolve(img);
};
img.onerror = (err) => {
console.error(err, url);
reject(null);
};
});
};
/**
* @description: 下载图片
* @param {string} url:下载地址
* @param {string} name:图片名称
* @return {*}
*/
const downloadImage = (url: string, name?: string) => {
const a: HTMLAnchorElement = document.createElement('a');
const event: MouseEvent = new MouseEvent('click');
a.download = name || 'photo';
a.href = url;
a.dispatchEvent(event);
};
/**
* @description: 文件生成临时地址
* @param {*} ArrayBuffer
* @param {*} type
* @return {*}
*/
interface TemFileAddress {
url: string;
revokeObjectURL: () => void;
}
const temFileAddress = (file: Blob, type?: string): TemFileAddress => {
const url = window.URL.createObjectURL(file);
const revokeObjectURL = () => {
window.URL.revokeObjectURL(url);
};
return {
url,
revokeObjectURL,
};
};
1.这是vue文件里面的主流程
img