1.需求原型
2.实现思路
- 将设计还原成react code
- 将需要下载的dom绘制成canvas
- 将canvas转成base64
- 最后将base64转成blob并复制到剪切板
3.技术选型
经调研,现在市面上有现成成熟的开源软件可以实现关键路径。
- qrcode.react 生成二维码
- html2canvas 将HTMLElement转成canvas
- clipboard-polyfill 将图片复制到剪切板 (有兼容性问题)
注:若有保存图片的需求可以借鉴canvas2image
4.关键代码
/** base64转Blob */
export const b64toBlob = (
b64Data: string,
type: string | null = null,
size: number | null = null,
) => {
const contentType = type || 'image/png';
const sliceSize = size || 512;
const byteCharacters = window.atob(b64Data.substring(b64Data.indexOf(',') + 1));
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
// eslint-disable-next-line no-plusplus
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
return new Blob(byteArrays, { type: contentType });
};
/** 复制图片到剪切板
* b64Data-base64
*/
export const imageClip = (b64Data: string, callBack?: () => void) => {
const item = new clipboard.ClipboardItem({
'image/png': b64toBlob(b64Data, 'image/png', 512),
});
clipboard.write([item]).then(() => {
if (callBack) {
callBack();
}
});
};
/** canvas配置 解决模糊问题
* 思路:通过把canvas容器扩大,再将和成的图片进行缩放
*/
const canvasConfig = useCallback((dom: HTMLElement) => {
const box = window.getComputedStyle(dom);
// DOM 节点计算后宽高
const width = parseInt(box.width, 10);
const height = parseInt(box.height, 10);
// 获取像素比
const scaleBy = 2;
// 创建自定义 canvas 元素
const canvas = document.createElement('canvas');
// 设定 canvas 元素属性宽高为 DOM 节点宽高 * 像素比
canvas.width = width * scaleBy;
canvas.height = height * scaleBy;
// 设定 canvas css宽高为 DOM 节点宽高
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
// 获取画笔
const context = canvas.getContext('2d');
// 将所有绘制内容放大像素比倍
context?.scale(scaleBy, scaleBy);
return { canvas };
}, []);
/** 复制二维码 *
const handleCopyQRCode = useCallback(() => {
const dom = document.getElementById('_downQRCode');
if (dom) {
html2canvas(dom as HTMLElement, canvasConfig(dom)).then((imgDom) => {
const base64 = imgDom.toDataURL();
imageClip(base64, () => {
Toast.success('已复制到剪切板', 2000);
});
});
}
}, [canvasConfig]);
5.在火狐、360兼容模式、IE等浏览器复制到剪切板有兼容性问题,可以换成下载思路。
/** 保存png */
export const saveAsPNG = (b64Data: string, fileName: string = 'download') => {
// 下载base64图片
const blob = b64toBlob(b64Data, 'image/png', 512);
// @ts-ignore
if (window?.navigator?.msSaveOrOpenBlob) {
// @ts-ignore
window.navigator.msSaveOrOpenBlob(blob, `${fileName}.png`);
} else {
const aLink = document.createElement('a');
const evt = document.createEvent('HTMLEvents');
evt.initEvent('click', true, true); // initEvent 不加后两个参数在FF下会报错 事件类型,是否冒泡,是否阻止浏览器的默认行为
aLink.download = fileName;
aLink.href = URL.createObjectURL(blob);
aLink.click();
}
};