今天完善一下浮墨笔记,实现分享memo功能,需要创建海报图片,用html-to-image完美解决问题。
仓库地址:bubkoo/html-to-image: ✂️ Generates an image from a DOM node using HTML5 canvas and SVG. (github.com)
实现
首先自己写好html,css就不展示了。。
1 2 3 4 5 6 7 8 9
| <div class="memo"> <div class="content"> <span class="time">2022/09/09 09:30:32</span> <span> 今天我吃饭了哈哈哈哈哈你好今天我吃饭了哈哈哈哈哈你好今天我吃饭了哈哈哈哈哈你好今天我吃饭了哈哈哈哈哈你好 </span> </div> <footer>✍️ by <b>sunzehui</b></footer> </div>
|
我这里框架用的是vue3
,在onMounted
里拿到dom
,然后生成图片。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import { onMounted } from 'vue'
const memoRef = ref(null);
const imgUrl = ref(null); onMounted(async () => { if (!show) return; await nextTick(); const node = unref(memoRef); if (!node) return;
htmlToImage .toCanvas(node, { pixelRatio: window.devicePixelRatio * 2, backgroundColor: "#eaeaea", }) .then((canvas) => canvas.toDataURL()) .then((url) => { imgUrl.value = url; }) .catch(function (error) { console.error("oops, something wents wrong!", error); }); });
|
此时template
里需绑定ref
,并且添加展示图片的地方。
1 2 3 4 5 6 7 8 9 10 11 12
| <div class="memo" ref="memoRef"> <img : src="imgUrl" alt="img" v-if="imgUrl" class="absolute" > <div class="content"> <span class="time">2022/09/09 09:30:32</span> <span> 今天我吃饭了哈哈哈哈哈你好今天我吃饭了哈哈哈哈哈你好今天我吃饭了哈哈哈哈哈你好今天我吃饭了哈哈哈哈哈你好 </span> </div> <footer>✍️ by <b>sunzehui</b></footer> </div>
|
img
元素在海报未生成完成时不应插入到dom
中所以使用v-if
。
为了方便展示,海报生成后立即覆盖原有dom
层级显示,提示用户长按保存图片。
遇到的问题
图片模糊
之前按照文档直接使用htmlToImage.toPng
,生成的图片较为模糊,即使设置质量100%。
对比:
- 直接生成png
- 转换
canvas
后提取图片base64
位置偏移
来自2022年10月份的补充:
如图,整个图片整体向右下角偏移了,不多不少正好是margin-top和margin:0 auto的偏移量。
根据网上的说法,大概是不支持有偏移的情况,他们描述的是脱离文档流下的top,left之类以及translate,但我用的margin,也存在这种情况,所以需要在截图之前清掉这些东西,于是我放弃了之前先展示dom,生成图片后展示图片覆盖dom的做法,直接将dom移出视口了(直接隐藏截不了图)
1 2 3 4
| .poster{ postion: absolute; left: -100%; }
|
为了保证不再有偏移,从待截图的dom复制一份,插入到body,将所有能偏移的属性reset。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| export async function getPoster( posterEl: Ref<HTMLElement | null> ): Promise<string | null> { await nextTick(); const targetDom = unref(posterEl); if (!targetDom) return null; window.scroll(0, 0); const copyDom = targetDom.cloneNode(true); copyDom.style.width = targetDom.scrollWidth + "px"; copyDom.style.height = targetDom.scrollHeight + "px"; copyDom.classList.add("copy-style"); document.body.appendChild(copyDom); const rect = copyDom.getBoundingClientRect(); try { const canvas = await htmlToImage.toCanvas(copyDom, { pixelRatio: window.devicePixelRatio * 2, backgroundColor: "#fff", x: rect.left, y: rect.top, useCORS: true, width: copyDom.clientWidth, height: copyDom.clientHeight, scrollY: 0, scrollX: 0, }); const dataUrl = canvas.toDataURL(); return dataUrl; } finally { document.body.removeChild(copyDom); } }
|
1 2 3 4 5 6 7 8
| .copy-style { top: 0 !important; left: 0 !important; margin: 0 !important; -webkit-transform: initial !important; transform: initial !important; }
|
最终效果就是正常了,没有偏移
效果
或许需要加个loading
😐