No more than code.
html/canvas 转图片并下载
| 总结
-
使用 canvas 的 api 时, 使用的都是绝对数值或坐标位置[不带单位]。若项目中使用 rem 适配,绘制 canvas 时,需将 rem 转换为 px:获取设备宽度及 UI 稿大小换算百分比 将转换后的数值传入 canvas 的方法中!如:设备高度/canvas 绘制高度 = 750/设计稿中 div 高度
-
图片加载异步;canvas 绘制异步
-
插件实现html2canvas
html2canvas(changeDOM, {
allowTaint: true, // 允许污染,被污染的 canvas 是没法使用 toDataURL() 转 base64 流的
useCORS: true, //【重要】开启跨域配置
logging: false, // 是否开启日志
canvas: canvas,
width: changeDOM.offsetWidth,
height: changeDOM.offwetHeight,
onrendered: function (canvas) {
var dataURL = canvas.toDataURL('image/png');
var img = new Image();
img.src = dataURL;
img.onload = function () {
console.log(img);
};
},
});
// github上解决清晰度问题:hidpi-canvas-polyfill-master
-
渲染图片:确保事件函数 onload 是在图片加载完成之前绑定上去的
-
生成的最上层图片不能加
pointerEvents:none
, 否则在微信中不能长按保存
| canvas 绘制 html 实例
// 获取页面元素
var DOM = document.querySelectorAll(".gainRecord")[0];
var userTxt = document.querySelectorAll(".record_user")[0];
var fordTxt = document.querySelectorAll(".record_ford")[0];
var saoTxt = document.querySelectorAll(".sao")[0];
var addOneTxt = document.querySelectorAll(".addOne")[0];
var recordImg = document.querySelectorAll(".recordImg")[0];
var avatorImg = document.querySelectorAll(".share_avator")[0];
var codeImg = document.querySelectorAll(".code")[0];
// 设置canvas
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
// 解决生成图片模糊问题
var devicePixelRatio = window.devicePixelRatio || 2; // 屏幕的设备像素比
// 浏览器在渲染canvas之前存储画布信息的像素比
var backingStoreRatio = context.webkitBackingStorePixelRatio ||
context.mozBackingStorePixelRatio ||
context.msBackingStorePixelRatio ||
context.oBackingStorePixelRatio ||
context.backingStorePixelRatio || 1;
// canvas的实际渲染倍率
var scale = devicePixelRatio / backingStoreRatio;
var width = 268;
var height = 382;
// 获取根据屏幕分辨率,来设置canvas的宽高以获得高清图片
canvas.width = width * ;
canvas.height = height * scale;
canvas.style.width = width + "px";
canvas.style.height = height + "px";
ctx.scale(scale, scale);
// 添加白色背景
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, 268, 382);
// 渲染文字:font设置格式相同,防止文字样式覆盖
ctx.textBaseline = "top"; // 解决文字显示不全/位置偏离
ctx.font = "bold 15px/1.6 PingFangSC-Regular"
ctx.fillStyle = "#13171E";
ctx.fillText(userTxt.innerHTML, 55, 265);
ctx.font = "normal 12px/1.6 PingFangSC-Regular";
ctx.fillStyle = "#353537";
ctx.fillText(fordTxt.innerHTML, 55, 285);
ctx.font = "normal 10px/1.6 PingFangSC-Regular";
ctx.fillStyle = "#878A8E";
ctx.fillText(saoTxt.innerHTML, 124, 337);
ctx.fillText(addOneTxt.innerHTML, 104, 355);
// 渲染图片;在html img标签上添加crossorigin="anonymous"解决src跨域
var imgList = [
{ img: recordImg, x: 0, y: 0, width: 268, height: 250 },
{ img: avatorImg, x: 15, y: 264, width: 34, height: 34 },
{ img: codeImg, x: 203, y: 316, width: 50, height: 50 },
]
recordImg.onload = () => { // 图片加载异步
imgList.map((ele, index) => {
if (ele.img == avatorImg) {
// 将正方形图片切成圆形
var width = ele.width;
var height = ele.height;
ctx.save();
ctx.beginPath(); //开始路径画圆,剪切处理
ctx.arc(width / 2 + ele.x, height / 2 + ele.y, width / 2, 0, Math.PI * 2, false);
ctx.clip(); //剪切路径
ctx.drawImage(ele.img, ele.x, ele.y, width, height);
ctx.restore(); //恢复状态
} else {
ctx.drawImage(ele.img, ele.x, ele.y, ele.width, ele.height);
}
})
// canvas 转成图片,放入DOM中
var dataURL = canvas.toDataURL('image/jpeg', 1);
var img = document.createElement("img");
img.src = dataURL;
img.setAttribute("id", "html_share_img");
img.setAttribute('crossOrigin', 'anonymous')
img.onload = function() { // 渲染图片成功,设置层级最高实现长按保存
img.style.width = '268px';
img.style.position = "fixed";
img.style.top = '.76rem';
img.style.left = '50%';
img.style.transform = 'translate(-50%, 0)';
img.style.opacity = 0;
img.style.zIndex = 1999;
document.body.appendChild(img);
}
}
| 图片下载
PC 端downloadFileByBase64()
实现点击下载
移动端长按保存
function downloadFileByBase64(base64, name) {
var myBlob = dataURLtoBlob(base64);
var myUrl = URL.createObjectURL(myBlob);
downloadFile(myUrl, name);
}
function dataURLtoBlob(dataurl) {
var arr = dataurl.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
}
function downloadFile(url, name = "What's the fuvk") {
var a = document.createElement('a');
a.setAttribute('href', url);
a.setAttribute('download', name);
a.setAttribute('target', '_blank');
let clickEvent = document.createEvent('MouseEvents');
clickEvent.initEvent('click', true, true); //initEvent 不加后两个参数在FF下会报错 事件类型,是否冒泡,是否阻止浏览器的默认行为
a.dispatchEvent(clickEvent);
}
| 绘制文字折行/省略号
// 绘制文字折行/省略号
const dealWords = (options) => {
options.ctx.setFontSize(options.fontSize); //设置字体大小
var allRow = Math.ceil(
options.ctx.measureText(options.word).width / options.maxWidth
); //实际总共能分多少行
var count = allRow >= options.maxLine ? options.maxLine : allRow; //实际能分多少行与设置的最大显示行数比,谁小就用谁做循环次数
var endPos = 0; //当前字符串的截断点
for (var j = 0; j < count; j++) {
var nowStr = options.word.slice(endPos); //当前剩余的字符串
var rowWid = 0; //每一行当前宽度
if (options.ctx.measureText(nowStr).width > options.maxWidth) {
//如果当前的字符串宽度大于最大宽度,然后开始截取
for (var m = 0; m < nowStr.length; m++) {
rowWid += options.ctx.measureText(nowStr[m]).width; //当前字符串总宽度
if (rowWid > options.maxWidth) {
if (j === options.maxLine - 1) {
//如果是最后一行
options.ctx.fillText(
nowStr.slice(0, m - 1) + '...',
options.x,
options.y + (j + 1) * 18
); //(j+1)*18这是每一行的高度
} else {
options.ctx.fillText(
nowStr.slice(0, m),
options.x,
options.y + (j + 1) * 18
);
}
endPos += m; //下次截断点
break;
}
}
} else {
//如果当前的字符串宽度小于最大宽度就直接输出
options.ctx.fillText(
nowStr.slice(0),
options.x,
options.y + (j + 1) * 18
);
}
}
};
dealWords({
ctx: ctx, //画布上下文
fontSize: 11, //字体大小
word: info.description, //需要处理的文字
maxWidth: 240, //一行文字最大宽度
x: 10, //文字在x轴要显示的位置
y: 222, //文字在y轴要显示的位置
maxLine: 3, //文字最多显示的行数
});
| 解决小程序 getImageInfo 无法获取 base64 图片信息
// 解决 getImageInfo 无法获取 base64 图片信息
function dealBase64(base64data, cb) {
const fsm = uni.getFileSystemManager();
const FILE_BASE_NAME = 'tmp_base64src'; // 自定义文件名
const [, format, bodyData] =
/data:image\/(\w+);base64,(.*)/.exec(base64data) || [];
if (!format) {
// format-图片后缀
return new Error('ERROR_BASE64SRC_PARSE');
}
const time = new Date().getTime(); // 小程序中 base64 生成本地缓存文件不会被覆盖掉,所以需要手动清除缓存文件,或以时间戳为文件目录之一
const filePath = `${wx.env.USER_DATA_PATH}/${time}.${format}`;
const buffer = wx.base64ToArrayBuffer(bodyData);
fsm.writeFile({
filePath,
data: buffer,
encoding: 'binary',
success() {
cb && cb(filePath);
},
fail() {
return new Error('ERROR_BASE64SRC_WRITE');
},
});
}
| 小程序绘制图片
// 绘制头像
drawBigImg(ctx, {
URL: info.userAvatar,
x: 10,
y: 16,
width: 27.7,
height: 27.7,
type: 'circle',
});
const drawBigImg = (ctx, config) => {
return new Promise((resolve, reject) => {
uni.getImageInfo({
src: config.URL,
success(image) {
console.log('success', config.URL);
let img = {
x: config.x,
y: config.y,
width: config.width,
height: config.height,
};
if (config.type == 'circle') {
ctx.save();
//开始路径画圆,剪切处理
ctx.beginPath();
ctx.arc(
img.width / 2 + img.x,
img.height / 2 + img.y,
img.width / 2,
0,
Math.PI * 2,
false
);
ctx.clip(); //剪切路径
ctx.drawImage(image.path, img.x, img.y, img.width, img.height);
ctx.restore(); //恢复状态
} else {
ctx.drawImage(image.path, img.x, img.y, img.width, img.height);
}
ctx.draw(true);
resolve();
},
fail(err) {
console.log(err);
uni.showToast({
// title: "图片加载失败",
title: err.errMsg,
duration: 2000,
icon: 'none',
});
reject();
},
});
});
};
| 文字居中
/**
* 文字居中
* @param {Object} _paint // context对象
* @param {Object} _text // 文字
* @param {Object} _fontSzie
* @param {Object} _color
* @param {Object} _height // 绘制区域的高度
* @param {Object} _width // 绘制区域的宽度
*/
function canvas_text(_paint, _text, _fontSzie, _color, _width, _height) {
_paint.save();
_paint.font = _fontSzie;
_paint.fillStyle = _color;
_paint.textAlign = 'center';
_paint.fillText(_text, _width, _height);
_paint.restore();
}
| 圆角/虚线
// 绘制圆角矩形
const drawRadiusRect = (ctx, x, y, width, height, r) => {
ctx.save();
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arcTo(x + width, y, x + width, y + r, r); // draw right side and bottom right corner
ctx.arcTo(x + width, y + height, x + width - r, y + height, r); // draw bottom and bottom left corner
ctx.arcTo(x, y + height, x, y + height - r, r); // draw left and top left corner
ctx.arcTo(x, y, x + r, y, r);
ctx.fillStyle = "rgba(255,255,255,0.6)";
ctx.fill();
ctx.restore();
};
// 绘制圆角矩形图片
drawRoundedImg('../../static/common/personcenter.png', ctx, 0, 0, 260, 150, 8);
// path-图片路径 r-圆角大小
const drawRoundedImg = (path, ctx, x, y, width, height, r) => {
ctx.save();
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arcTo(x + width, y, x + width, y + r, r);
ctx.arcTo(x + width, y + height, x + width - r, y + height, r);
ctx.arcTo(x, y + height, x, y + height - r, r);
ctx.arcTo(x, y, x + r, y, r);
ctx.clip();
ctx.drawImage(path, x, y, width, height);
ctx.restore();
};
// 绘制虚线
ctx.beginPath();
ctx.setLineDash([5, 1], 0);
ctx.setStrokeStyle('#D0D0D0');
ctx.setLineWidth(1);
ctx.moveTo(10, 301);
ctx.lineTo(249, 301);
ctx.stroke();
// 绘制圆型图片
const drawCircleImg = (img, ctx, x, y, width, height) => {
ctx.save();
ctx.beginPath();
ctx.arc(width / 2 + x, height / 2 + y, width / 2, 0, Math.PI * 2, false);
ctx.clip();
ctx.drawImage(img, x, y, width, height);
ctx.restore();
};