一、背景
微信活动H5的最后一页,永远是雷打不动的——长按保存海报到手机。
二、解决思路
为啥非得长按保存图片,而不弄个“点击按钮保存到手机”呢?这是因为微信浏览器作了一些安全限制,不支持通过脚本保存图片到本地。那么就只能使用微信浏览器自己的保存图片功能,长按保存了。
解决思路如下:
1、先用CSS、html排版好海报样式;
2、使用 html2canvas 库将 DOM 转化成图片;
3、展示图片。
三、解决方法
(本示例使用Vue3 + TS)
1、先用CSS、html排版好海报样式
用CSS和html根据设计稿排好版,并用一个容器(如div)把所有海报内容包起来。这里使用的Vue3,加上了ref属性方便后面引用这个DOM元素。
<div class="container" ref="containerDom">
<div>我是海报</div>
<img src="..." />
<div class="button-group">一些按钮</div>
</div>
2、使用 html2canvas 库将 DOM 转化成图片
需要使用到 html2canvas 这个库,官网:html2canvas - Screenshots with JavaScript。官网介绍十分简短,简短到我看完还是不知道怎么用。。所以推荐先看下面的教程,再看官方文档来查配置项。
1)项目安装依赖:
// 使用 npm:
npm install html2canvas --save
// 使用 yarn:
yarn add html2canvas
2)在需要的地方引入:
import html2canvas from 'html2canvas';
3)编写生成海报的函数:
/**
* @params containerDom: 包裹待转化的DOM,
* 即本例中的 <div class="container" ref="containerDom">
*/
const makePoster = async (containerDom: HTMLElement | null | undefined) => {
let posterUrl = '';
if (!containerDom) {
console.error('containerDom为空: ');
return posterUrl;
}
// 设置配置项,可以在官方文档查
const options = {
scale: window.devicePixelRatio, // 放大倍数,消除截图锯齿
width: gradesDom?.offsetWidth, // 生成的图片宽度,单位px
height: posterDom.offsetHeight + 60, // 生成的图片高度,单位px
backgroundColor: null, // 设置背景色,null表示透明。若不设置此项,默认是白色背景
};
try {
const canvas = await html2canvas(containerDom, options);
posterUrl = canvas.toDataURL('image/png');
} catch (err) {
console.info('制作海报出错了:', err);
}
return posterUrl;
};
注:这里若生成图片失败,可以检查一下是否containerDom为空、containerDom中是否使用了 html2canvas 不支持的 css 属性(查看官方文档)。
3、展示图片
在合适的时候(如进入页面时或点击某按钮后)调用在上一步编写的函数,我们就能拿到图片的Url,就可以为所欲为啦~ 只需用一个 img 元素,设置其 src 为该 url 即可。
<!-- vue中,记得在src前加上冒号":"才是绑定变量 -->
<img :src="posterUrl" />
4、完整代码
仅作示意
<template>
<div class="container" ref="containerDom">
<div>我是海报</div>
<img src="..." />
<div class="button-group">
<button @click="makePoster">生成海报</button>
</div>
</div>
<img :src="posterUrl" />
</template>
<script lang="ts">
import html2canvas from 'html2canvas';
import { defineComponent, ref } from 'vue'; // vue3 + ts 才会用到
export default defineComponent({
setup() {
const containerDom = ref(null);
const makePoster = async (containerDom: HTMLElement | null | undefined) => {
let posterUrl = '';
if (!containerDom) {
console.error('containerDom为空: ');
return posterUrl;
}
const options = {
scale: window.devicePixelRatio,
width: gradesDom?.offsetWidth,
height: posterDom.offsetHeight + 60,
backgroundColor: null,
};
try {
const canvas = await html2canvas(containerDom, options);
posterUrl = canvas.toDataURL('image/png');
} catch (err) {
console.info('制作海报出错了:', err);
}
return posterUrl;
};
// vue3 setup的return
return {
containerDom, // 这个一定记得return,不然永远是空!
makePoster,
posterUrl,
};
},
});
</script>
四、补充
有一个比较常用的 html2canvas 配置项:ignoreElements。可以用来过滤掉 containerDom 中不想到图片里的内容,如一些操作按钮。
其是一个返回值为 boolean 的函数,会应用到 containerDom 的每个后代元素上。html2canvas 在生成 canvas 前,会把 containerDom 深拷贝一份。若对某后代元素执行 ignoreElements 时返回值是 true,则该元素不会被拷贝,自然也不会出现在最后的图片中。
const makePoster = async (containerDom: HTMLElement | null | undefined) => {
// ...
const options = {
// element 是 containerDom 的后代元素
// 这里类名包含 “button-group” 的元素将不会出现在生成的图片中
ignoreElements: (element: Element) => {
const isButtons = element.classList.contains('button-group');
return isButtons;
},
}
// ...
};
有不正确之处还望指出~