我经常思考,在面临一个不确定问题时,以往的经验究竟有无辅助作用?如果把经验遗忘会产生何种程度的影响?在上下求索未果之后,如何找回曾经的感觉,恰若灵光一现?凡此种种,终是要思考总结的,这篇文章便是我的反思之作。
本篇文章会记述一些实用的svg与png之间的转换技巧并强调一种思考原则。
概述
技巧
svg和png图片转换和下载
解决chrome data url too large下载问题
解决@ViewChild未及时刷新问题
原则
永远从问题最近的地方开始分析
理解下面这些内容的前提是具备一些Angular的编程基础,要求大致处于能自定义component的水平。
假意需求
当我说“假意需求”的时候,其实是将解决方案视作眼下的需求,目的是方便理解。在这个项目中,我们需要把页面上的已经存在的svg元素转换成可下载的svg和png链接。svg是矢量图,适合打印成海报;而png清晰度有限,用作在线预览。
背景知识
下面是svg(Scalable Vector Graphics)和canvas在编程方式、技术原理、使用范围以及转换程度这4个维度上的对比和评估。这些知识是理解实现svg转换为png的基础。
编程方式
svg是矢量图形语言,canvas提供画布标签和绘制API;
svg提供各种图形,滤镜和动画。canvas只有绘制API,相对原始。
技术原理
svg是矢量图,提供了很多图形,还有完整的动画,事件机制,本身可以独立使用;
canvas基于像素,是一种HTML元素,只能通过脚本绘制。
适用范围
svg被主流浏览器和svg阅读器支持,canvas只有主流浏览器支持;
svg适用于大面积渲染区域的程序和静态文档,如google地图。canvas适合小范围图像密集型场景,如游戏。
转换程度
svg较难以转换成png或者jpeg格式的图片,不过canvas较容易。
技巧
假设主页面 app.component.html 面已经有一个component,它的内容如下:
<app-template #template></app-template>
其中 <app-template></app-template> 是一个自定义的component,它代表了一个svg文件,svg的内容存放在 template.component.html 中,而 template.component.ts 的定义如下:
// template.component.ts @Component({ selector: 'app-template', templateUrl: './template.component.html', styleUrls: ['./template.component.scss'], }) export class TemplateComponent implements OnInit { ngOnInit() { } }
当然,这个template.component需要在 app.module.ts 中声明后才能在 app.component.html 中使用。
注意, #template 是Angular5之后引入的语法,它的全称是 ,功能在于引用其所指向的DOM元素。
接下来要解决的就是如何在component中引用页面上的svg元素并将它转化成png格式的图片。
svg和png图片转换和下载
1. 获取元素
Angular中提供一种叫做 ViewChild 的注解,可以帮助我们引用到页面中的svg元素,此处就是 #template .
@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], }) export class AppComponent implements OnDestroy { @ViewChild('template') template: { svgRef: ElementRef }; ngOnDestroy(): void { } }
获取svg元素的方式为 this.template.svgRef.nativeElement .
2. 图片转换
有了svg元素,接下来需要考虑的是如何对其编程。svg和html在浏览器的内存中都是以DOM树的形式存在,所以想要对svg进行编程,就得利用svg的. 比如说我们要获取 <svg> 元素中的各项属性,就需要使用 SVGSVGElement编程接口 。
svg转换成png并不直接,但是我们知道canvas转换成png非常简单。所以有种思路是将svg转换成canvas再转成png. canvas有个 drawImage 函数,可以将图片绘制到画布上,该函数的输入源是 HTMLImageElement 或者另外的canvas元素。
也就是说,如果我们能把svg转换成 HTMLImageElement 即 <img> ,那么上述过程就顺理成章连成一串了。
第一步是将svg元素转换成DataURL.
private toSvgDataURL(viewerSvg: SVGSVGElement): string { const svg = viewerSvg.cloneNode(true) as SVGSVGElement; svg.setAttribute('width', '600px'); const base64Data = btoa(unescape(encodeURIComponent(svg.outerHTML))); return `data:image/svg+xml;base64,${base64Data}`; }
第二步是将DataURL转换成 <img> .