[html] 请使用canvas画一个渐变的长方形
// 普通canvas绘图工具类// umd适配多种引入方式
(function(root, factory) {if (typeof define === 'function' && define.amd) {// AMDdefine(['CanvasTool'], factory);} else if (typeof exports === 'object' && typeof module === 'object') {// Node, CommonJS之类的module.exports = factory();} else {// 浏览器全局变量(root 即 window)root.CanvasTool = factory();}
}(window, function() {// 方法const textBreakline = Symbol('textBreakline');const validateRlues = Symbol('validateRlues');const letterSpacingText = Symbol('letterSpacingText');const setFillStyle = Symbol('setFillStyle');const setStrokeStyle = Symbol('setStrokeStyle');const setFontSize = Symbol("setFontSize");class CanvasTool {constructor(ctx, scale) {this.ctx = ctx;this.scale = scale || 1;}/*** 画菱形* @param {Object} rhombObj 传入菱形的参数* sx: 开始的x轴* sy: 开始的y轴* cx: 中心位置的x轴* cy: 中心位置的y轴* width: 宽度* height: 高度* bgColor: 背景色*/rhomb(rhombObj) {let { sx, sy, cx, cy, width, height, bgColor } = rhombObj;// 参数校验规则let ruleMap = new Map([[sx, { type: 'number', param: 'sx' }],[sy, { type: 'number', param: 'sy' }],[cx, { type: 'number', param: 'cx' }],[cy, { type: 'number', param: 'cy' }],[width, { type: 'number', param: 'width' }],[height, { type: 'number', param: 'height' }],[bgColor, { type: 'string', param: 'bgColor' }]]);this[validateRlues](ruleMap);// 设置默认值sx = sx || 0;sy = sy || 0;cx = cx || 0;cy = cy || 0;bgColor = bgColor || '#fff';this.ctx.translate(cx, cy);this.ctx.rotate(45 * Math.PI / 180);// 修正画布坐标this.ctx.translate(-cx, -cy);this.ctx.fillStyle = bgColor;this.ctx.fillRect(sx, sy, width, height);this.ctx.save();}/*** 画直线* @param { Object } lineObj 传入的直线对象* line 画直线* sx: 开始x轴* sy: 开始y轴* ex: 结束x轴* ey: 结束y轴* strokeWidth: 线宽* strokeStyle: 线条样式*/line(lineObj) {// 获取传入的文本对象let { sx, sy, ex, ey, strokeWidth, strokeStyle } = lineObj;// 参数校验规则let ruleMap = new Map([[sx, { type: 'number', param: 'sx' }],[sy, { type: 'number', param: 'sy' }],[ex, { type: 'number', param: 'ex' }],[ey, { type: 'number', param: 'ey' }],[strokeWidth, { type: 'number', param: 'strokeWidth' }],[strokeStyle, { type: 'string', param: 'strokeStyle' }]]);this[validateRlues](ruleMap);// 设置默认值sx = sx || 0;sy = sy || 0;ex = ex || 100;ey = ey || 100;strokeStyle = strokeStyle || '#000';strokeWidth = strokeWidth || 2;this.ctx.beginPath();this.ctx.moveTo(sx, sy);this.ctx.lineTo(ex, ey);this[setStrokeStyle](strokeWidth, strokeStyle);this.ctx.stroke();}/*** 画三角形* @param { Object } triangleObj 传入的三角形对象*/triangle(triangleObj) {// 获取传入的三角形对象let { fx, fy, sx, sy, tx, ty, stroke, strokeWidth, strokeStyle, fill, bgColor } = triangleObj;// 参数校验规则let ruleMap = new Map([[fx, { type: 'number', param: 'fx' }],[fy, { type: 'number', param: 'fy' }],[sx, { type: 'number', param: 'sx' }],[sy, { type: 'number', param: 'sy' }],[tx, { type: 'number', param: 'tx' }],[ty, { type: 'number', param: 'ty' }],[strokeWidth, { type: 'number', param: 'strokeWidth' }],[bgColor, { type: 'string', param: 'bgColor' }],[strokeStyle, { type: 'string', param: 'strokeStyle' }],[stroke, { type: 'boolean', param: 'stroke' }],[fill, { type: 'boolean', param: 'fill' }]]);this[validateRlues](ruleMap);// 设置默认值fx = fx || 0;fy = fy || 0;bgColor = bgColor || '#fff';strokeStyle = strokeStyle || '#000';fill = fill || false;strokeWidth = strokeWidth || 1;this.ctx.beginPath();this.ctx.moveTo(fx, fy);this.ctx.lineTo(sx, sy);this.ctx.lineTo(tx, ty);// 填充颜色fill && (this[setFillStyle](bgColor),this.ctx.fill());stroke && (this[setStrokeStyle](strokeWidth, strokeStyle),this.ctx.stroke());}/*** rect 绘制矩形的方法* @param { Object } rectObj 传入的矩形对象* x 页面x轴的坐标* y 页面y轴的坐标* width 矩形的宽度* height 矩形的高度* bgColor 矩形的背景色* fill 是否填充* strokeStyle 填充边框的颜色* stroke 是否需要边框*/rect(rectObj) {// 获取传入的文本对象let { x, y, width, height, bgColor, fill, stroke, strokeStyle, strokeWidth } = rectObj;// 参数校验规则let ruleMap = new Map([[x, { type: 'number', param: 'x' }],[y, { type: 'number', param: 'y' }],[width, { type: 'number', param: 'width' }],[height, { type: 'number', param: 'height' }],[strokeWidth, { type: 'number', param: 'strokeWidth' }],[bgColor, { type: 'string', param: 'bgColor' }],[strokeStyle, { type: 'string', param: 'strokeStyle' }],[stroke, { type: 'boolean', param: 'stroke' }],[fill, { type: 'boolean', param: 'fill' }]]);this[validateRlues](ruleMap);// 设置默认值x = x || 0;y = y || 0;bgColor = bgColor || '#fff';strokeStyle = strokeStyle || '#000';fill = fill || false;strokeWidth = strokeWidth || 1;this.ctx.rect(x, y, width, height);fill && (this[setFillStyle](bgColor),this.ctx.fill());stroke && (this[setStrokeStyle](strokeWidth, strokeStyle),this.ctx.strokeRect(x, y, width + strokeWidth, height + strokeWidth));}/*** 绘制圆角矩形* @param {number} x 圆角矩形选区的左上角 x坐标* @param {number} y 圆角矩形选区的左上角 y坐标* @param {number} w 圆角矩形选区的宽度* @param {number} h 圆角矩形选区的高度* @param {number} r 圆角的半径* @memberof CanvasTool*/roundRect(roundRectObj) {let { x, y, width: w, height: h, r, bgColor } = roundRectObj;// 参数校验规则let ruleMap = new Map([[x, { type: 'number', param: 'x' }],[y, { type: 'number', param: 'y' }],[w, { type: 'number', param: 'width' }],[h, { type: 'number', param: 'height' }],[r, { type: 'number', param: 'r' }],[bgColor, { type: 'string', param: 'bgColor' }]]);this[validateRlues](ruleMap);// 开始绘制this.ctx.beginPath();this[setFillStyle](bgColor),// 左上角this.ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5);// border-topthis.ctx.moveTo(x + r, y);this.ctx.lineTo(x + w - r, y);this.ctx.lineTo(x + w, y + r);// 右上角this.ctx.arc(x + w - r, y + r, r, Math.PI * 1.5, Math.PI * 2);// border-rightthis.ctx.lineTo(x + w, y + h - r);this.ctx.lineTo(x + w - r, y + h);// 右下角this.ctx.arc(x + w - r, y + h - r, r, 0, Math.PI * 0.5);// border-bottomthis.ctx.lineTo(x + r, y + h);this.ctx.lineTo(x, y + h - r);// 左下角this.ctx.arc(x + r, y + h - r, r, Math.PI * 0.5, Math.PI);// border-leftthis.ctx.lineTo(x, y + r);this.ctx.lineTo(x + r, y);this.ctx.fill();};/*** square 绘制正方形的方法* @param { Object } squareObj 传入的圆形对象* 参数同矩形的参数一致,只需要传入width就可以了*/square(squareObj) {squareObj.height = squareObj.width;this.rect(squareObj);}/*** arc 绘制圆形的方法* @param { Object } arcObj 传入的圆形对象* x 圆心的x轴* y 圆心的y轴* r 半径* sAngle 开始弧度* eAngle 终止弧度* counterclockwise 弧度方向是否是逆时针* fill 是否填充* storke 是有边框* bgColor 背景色* strokeStyle 边框色*/arc(arcObj) {let { x, y, r, sAngle, eAngle, counterclockwise, fill, bgColor, strokeStyle, strokeWidth, storke } = arcObj;// 参数校验规则let ruleMap = new Map([[x, { type: 'number', param: 'x' }],[y, { type: 'number', param: 'y' }],[r, { type: 'number', param: 'r' }],[sAngle, { type: 'number', param: 'sAngle' }],[eAngle, { type: 'number', param: 'eAngle' }],[strokeWidth, { type: 'number', param: 'strokeWidth' }],[counterclockwise, { type: 'number', param: 'counterclockwise' }],[fill, { type: 'boolean', param: 'fill' }],[storke, { type: 'boolean', param: 'storke' }],[bgColor, { type: 'string', params: 'bgColor' }],[strokeStyle, { type: 'string', params: 'strokeStyle' }],]);this[validateRlues](ruleMap);// 设置默认值x = x || 0;y = y || 0;sAngle = sAngle || 0;eAngle = eAngle || 0;fill = fill || false;bgColor = bgColor || '#000';this.ctx.beginPath();this.ctx.arc(x, y, r, sAngle * Math.PI, eAngle * Math.PI, counterclockwise);// 圆的边框暂时不生效storke && (this[setStrokeStyle](strokeWidth, strokeStyle),this.ctx.stroke());fill && (this[setFillStyle](bgColor),this.ctx.fill());}/*** 绘制圆形头像* @param img 图片资源* @param opts 参数* @param beforeFn 函数钩子* @param afterFn 函数钩子*/circleAvatar(circleAvatarObj) {let { img, opts, beforeFn, afterFn } = circleAvatarObj;let { x, y, r } = opts;// 参数校验规则let ruleMap = new Map([[x, { type: 'number', param: 'x' }],[y, { type: 'number', param: 'y' }],[r, { type: 'number', param: 'r' }]]);this[validateRlues](ruleMap);(isFunction(beforeFn)) && beforeFn();this.drawCircle({x,y,r});ctx.clip();ctx.drawImage(img, x, y, r);ctx.restore();(isFunction(afterFn)) && afterFn();return this;}/*** text 绘制文本的方法 * text 文本* x 页面x轴坐标* y 页面y轴坐标* width 文本的最大宽度* color 颜色* fontSize 字号* align 对其方式* textBaseline 文本的基线* wrap 是否换行* lineHeight 行高* letterSpacing 字间距*/text(textObj) {// 获取传入的文本对象let { text, color, fontSize, fontStyle, align, x, y, width, textBaseline, wrap, lineHeight, letterSpacing } = textObj;// 参数校验规则let ruleMap = new Map([[text, { type: 'string', param: 'text' }],[color, { type: 'string', param: 'color' }],[textBaseline, { type: 'string', param: 'textBaseline' }],[align, { type: 'string', param: 'align' }],[fontStyle, { type: 'string', param: 'fontStyle' }],[fontSize, { type: 'number', param: 'fontSize' }],[x, { type: 'number', param: 'x' }],[y, { type: 'number', param: 'y' }],[width, { type: 'number', param: 'width' }],[lineHeight, { type: 'number', param: 'lineHeight' }],[letterSpacing, { type: 'number', param: 'letterSpacing' }],[wrap, { type: 'boolean', param: 'wrap' }],]);this[validateRlues](ruleMap);// 设置默认值color = color || '';fontSize = fontSize || 16;align = align || 'center';x = x || 0;y = y || 0;width = width || 0;wrap = wrap || false; // 默认不换行letterSpacing = letterSpacing || 0;// 绘图this[setFillStyle](color);this[setFontSize](fontSize * this.scale);this.ctx.textAlign = align;textBaseline && (this.ctx.setTextBaseline = textBaseline);!wrap && !letterSpacing && this.ctx.fillText(text, x, y, width);wrap && this[textBreakline](text, x, y, width, lineHeight);letterSpacing && this[letterSpacingText](text, align, x, y, letterSpacing);}// -------私有属性--------/*** 设置填充颜色*/[setFillStyle](fillStyle) {this.ctx.fillStyle = fillStyle;}/*** 设置边框颜色 / 宽度*/[setStrokeStyle](strokeWidth, strokeStyle) {this.ctx.lineWidth = strokeWidth;this.ctx.strokeStyle = strokeStyle;}/*** 设置字体大小*/[setFontSize](fontSize, fontStyle = "Microsoft YaHei") {console.log(fontSize, fontStyle);this.ctx.font = `${fontSize}px ${fontStyle}`;}/*** textBreakline 文本换行功能,支持行高* @param {String} text 文本* @param {Number} x 页面x轴的位置* @param {Number} y 页面y轴的位置* @param {Number} maxWidth 最大宽度* @param {Number} lineHeight 行高*/[textBreakline](text, x, y, maxWidth, lineHeight) {// 字符分隔为数组let arrText = text.split('');let line = '';for (let n = 0; n < arrText.length; n++) {let testLine = line + arrText[n];let metrics = this.ctx.measureText(testLine);let testWidth = metrics.width;if (testWidth > maxWidth && n > 0) {this.ctx.fillText(line, x, y);line = arrText[n];y += lineHeight;} else {line = testLine;}}this.ctx.fillText(line, x, y);}/*** 文本支持字间距* @param {String} text 文本* @param {Number} x 页面x轴的位置* @param {Number} y 页面y轴的位置* @param {Number} letterSpacing 字间距*/[letterSpacingText](text, textAlign, x, y, letterSpacing) {let context = this.ctx;// let canvas = context.canvas;// if (!letterSpacing && canvas) {// letterSpacing = parseFloat(window.getComputedStyle(canvas).letterSpacing);// }// if (!letterSpacing) {// return this.fillText(text, x, y);// }let arrText = text.split('');let align = textAlign || 'left';// 这里仅考水平排列let originWidth = context.measureText(text).width;// 应用letterSpacing占据宽度let actualWidth = originWidth + letterSpacing * (arrText.length - 1);// 根据水平对齐方式确定第一个字符的坐标if (align == 'center') {x = x - actualWidth / 2;} else if (align == 'right') {x = x - actualWidth;}// 临时修改为文本左对齐context.textAlign = 'left';// 开始逐字绘制arrText.forEach(function(letter) {let letterWidth = context.measureText(letter).width;context.fillText(letter, x, y);// 确定下一个字符的横坐标x = x + letterWidth + letterSpacing;});// 对齐方式还原context.textAlign = align;}// 校验功能[validateRlues](ruleMap) {try {for (let [key, val] of ruleMap.entries()) {if (key && typeof key != val.type) {throw new Error(val.param + '的参数类型错误,要求是' + val.type + '类型');// 把所有参数都校验一遍continue;}}} catch (err) {console.error(err);}}}// 暴露公共方法return CanvasTool;
}));
个人简介
我是歌谣,欢迎和大家一起交流前后端知识。放弃很容易,
但坚持一定很酷。欢迎大家一起讨论
主目录
与歌谣一起通关前端面试题