I often think about whether past experiences have any auxiliary effects when facing an uncertain problem? What extent will the impact of forgetting experience have? After searching for the whole country without success, how can I find my past feeling? It seems like a flash of inspiration? All of these things must be considered and summarized. This article is my reflection.
This article will record some practical conversion techniques between svg and png and emphasize a thinking principle.
Overview
Skill
- svg and png image conversion and download
- Solve the problem of downloading chrome data url too large
- Solve the problem of @ViewChild not refreshing in time
in principle
Always start analyzing from the nearest point of the problem
The premise for understanding the following content is to have some Angular programming foundation and requires that it is roughly at the level of being able to customize the component.
Fake demand
When I say "fake demand", I actually regard the solution as the current demand, and the purpose is to facilitate understanding. In this project, we need to convert the existing svg elements on the page into downloadable svg and png links. svg is a vector image that is suitable for printing as a poster; while png has limited clarity and is used as an online preview.
Background knowledge
Below is the comparison and evaluation of svg (Scalable Vector Graphics) and canvas in four dimensions: programming method, technical principles, scope of use and degree of conversion. This knowledge is the basis for understanding the implementation of svg conversion to png.
Programming method
svg is a vector graphics language, and canvas provides canvas tags and drawing APIs;
svg offers a variety of graphics, filters and animations. canvas only draw API, relatively primitive.
Technical Principles
svg is a vector diagram, providing a lot of graphics, as well as complete animations, event mechanisms, which can be used independently;
Canvas is based on pixels and is an HTML element that can only be drawn through scripts.
Scope of application
svg is supported by mainstream browsers and svg readers, and canvas is only supported by mainstream browsers;
svg is suitable for programs and static documents in large-area rendered areas, such as Google Maps. Canvas is suitable for small-scale image-intensive scenarios, such as games.
Conversion degree
SVG is difficult to convert to png or jpeg images, but canvas is easier.
Skill
Suppose there is a component on the main page, and its content is as follows:
<app-template #template></app-template>
Where <app-template></app-template> is a custom component, which represents an svg file, and the content of svg is stored in , and the definition is as follows:
// @Component({ selector: 'app-template', templateUrl: './', styleUrls: ['./'], }) export class TemplateComponent implements OnInit { ngOnInit() { } }
Of course, this needs to be declared in .
Note that #template is a syntax introduced after Angular5, and its full name isTemplate reference variable (#var) , the function is to refer to the DOM element it points to.
The next thing to solve is how to reference the svg element on the page in the component and convert it into an image in png format.
svg and png image conversion and download
1. Get elements
Angular provides an annotation called ViewChild, which can help us reference the svg element in the page, here is #template.
@Component({ selector: 'app-root', templateUrl: './', styleUrls: ['./'], }) export class AppComponent implements OnDestroy { @ViewChild('template') template: { svgRef: ElementRef }; ngOnDestroy(): void { } }
The way to get the svg element is .
2. Picture conversion
With the svg element, the next thing to consider is how to program it. svg and html both exist in the form of a DOM tree in the browser memory, so if you want to program svg, you have to use svgDOM interface. For example, if we want to obtain various attributes in the <svg> element, we need to use SVGSVGElementProgramming interface 。
Converting svg to png is not direct, but we know that converting canvas to png is very simple. So there is a way to convert svg to canvas and then convert it to png. canvas has a drawImage function, which can draw the image on the canvas. The input source of the function is HTMLImageElement or another canvas element.
In other words, if we can convert svg into HTMLImageElement, i.e. <img> , then the above process will naturally be connected into a series.
The first step is to convert the svg element intoDataURL.
private toSvgDataURL(viewerSvg: SVGSVGElement): string { const svg = (true) as SVGSVGElement; ('width', '600px'); const base64Data = btoa(unescape(encodeURIComponent())); return `data:image/svg+xml;base64,${base64Data}`; }
The second step is to convert the DataURL to <img> .
function loadImage(url: string): Observable<HTMLImageElement> { const result = new Subject<HTMLImageElement>(); const image = ('img'); = url; ('load', () => { (image); }); return (); }
The third step is to convert <img> into canvas.
private toPngDataURL(img: HTMLImageElement): string { const canvas = ('canvas'); = ; = ; ('2d').drawImage(img, 0, 0); return ('image/png'); }
The conversion of canvas to png image is the above-mentioned call toDataURL.
3. Image download
The above three steps can be combined.
private generateDownloadUrl() { const svgDataURL = (); loadImage(svgDataURL) .pipe(map()) .subscribe(url => { = url; = svgDataURL; }); }
The href attribute of the <a> element can accept DataURL, so we assign svg dataURL and png dataURL to the member variables pngUrl and svgUrl. Finally, the download attribute is marked to indicate that this is a download link.
<a [href]="svgUrl" target="_blank" download="">download SVG Version</a> <a [href]="pngUrl" target="_blank" download="">download PNG Version</a>
Solve the problem of downloading chrome data url too large
The above process looks smooth and smooth, but in fact, once the image is too large, the Chrome browser will throw a network error when downloading. This is a bug that has existed in the chrome/chormium kernel for a long time. The detour scheme given on * is replaced by (blob).
private toSvg(viewerSvg: SVGSVGElement): string { const svg = (true) as SVGSVGElement; ('width', '600px'); const blob = new Blob([], {type: 'image/svg+xml'}); const url = (blob); return url; }
The processing of png can also be very flexible.
private toPng(img: HTMLImageElement): Observable<string> { const canvas = ('canvas'); = ; = ; ('2d').drawImage(img, 0, 0); const result = new Subject<string>(); (blob => { const url = (blob); (url); }); return (); }
However, because of the browser's security warning, the url needs to be sanitize before it can be released. This can be imported into DomSanitizer in Angular.
import {DomSanitizer, SafeResourceUrl} from '@angular/platform-browser'; ... constructor(private sanitizer: DomSanitizer) { }
The original code must return SafeResourceUrl.
private toSvg(viewerSvg: SVGSVGElement): SafeResourceUrl { const svg = (true) as SVGSVGElement; ('width', '600px'); const blob = new Blob([], {type: 'image/svg+xml'}); const url = (blob); const safeUrl = (url); return safeUrl; }
private toPng(img: HTMLImageElement): Observable<SafeResourceUrl> { const canvas = ('canvas'); = ; = ; ('2d').drawImage(img, 0, 0); const result = new Subject<SafeResourceUrl>(); (blob => { const url = ((blob)); (url); }); return (); }
The original merge operation is modified accordingly.
private generateDownloadUrl() { = (); const svgDataURL = (); loadImage(svgDataUrl) .pipe(flatMap()) // There is a pit here .subscribe(url => { = url; }); }
It is worth noting that the original pipe map was changed to flatMap because toPng returns an Observable, not a simple value.
This seems to be fine, but as in the comments of the above code: There is a pit here. Where is the pit? I will discuss the principles later, and now I will put it aside and enter the next technical topic.
Solve the problem of @ViewChild not refreshing in time
@ViewChild gets page elements that may not be up-to-date, Angular's Change detection takes time to complete refresh, so there is a short delay. This is intolerable to my program. Although the delay cannot be tolerated, it is still OK to wait for refresh before processing the image, so the solution is to wait for a second before performing image conversion.
private waitForViewChildReady() { return new Promise<string>((resolve) => { const wait = setTimeout(() => { clearTimeout(wait); resolve('workaround!'); }, 1000); }); }
The final program is called as follows.
() .then(()) .catch(err => (err))
in principle
Principles are used to guide practice.
Always start analyzing from the nearest point of the problem
Don't use tactical diligence to conceal strategic laziness
I am not very familiar with Angular personally. I encountered some pitfalls in the process of implementing the svg and png image download functions. These pitfalls are deep and shallow. The deep ones are directly oriented towards * programming and bypassed, and the shallow ones are solved by personal ability. However, overconfidence in solving these shallow pits has caused my thinking to become lazy and led to long-term waste.
The shallow pit here is Javascript's infamous this scope problem.
Recalling the code that has pitfalls on it,
loadImage(svgDataUrl) .pipe(flatMap()) // There is a pit here .subscribe(url => { = url; });
The code of toPng is as follows,
private toPng(img: HTMLImageElement): Observable<SafeResourceUrl> { const canvas = ('canvas'); = ; = ; ('2d').drawImage(img, 0, 0); const result = new Subject<SafeResourceUrl>(); (blob => { const url = ((blob)); (url); }); return (); }
When the program is running, an error was thrown cannot read bypassSecurityTrustResourceUrl of undefined.
The first reaction was whether I wrote the variable name incorrectly. After repeated verification, I found that I didn't write it wrong. However, this step is actually completely unnecessary, because these variables are all supplemented by the editor.
Immediately afterwards, I inserted () in the toBlob method, and the result printed after running is undefined. What can this mean? The program has ended here? In fact, this approach is not necessary, because the error message on the console clearly indicates that the code has been executed and an error occurred.
Then I started thinking, “Is the injection method of Angular I wrote is wrong?” After searching through the official Angular documentation and examples, I was sure there was no problem with the injection method. This step is beneficial because you are not familiar with Angular itself, and searching documents is a reasonable behavior, but the solution idea is too far from the goal, so the program problem should be solved through debug.
In desperation, I began to suspect that there was a problem with the package dependency download, so I used the stupidest method to delete node_modules and then download all the dependencies again. This is a time-consuming operation, and the biggest waste happens here. I analyzed the original basic principles of summarizing the exploration problems and started from the nearest path and completely forgot it. After the attempt was fruitless, I did not jump out of the horns, and forgot to spend time to empty myself. The principle was still confusing until I finally gave up.
The next morning, I drank a cup of coffee and my mind became clearer. In addition to the toPng method, I inserted () and found that this object appeared intact on the command line. At this moment, I suddenly felt inspiration. I recalled an article about Javascript scope a few years ago. Isn’t it a problem with this pointer?
loadImage(svgDataUrl) .pipe(flatMap((this))) // Note here bind(this) .subscribe(url => { = url; });
So use bind(this) to lock the pointer of this, and then find that the program is running normally, everything suddenly becomes clear. It is worth mentioning that this is just the cheapest fix, and the preferable way is to write the full function body.
loadImage(svgDataUrl) .pipe(flatMap(img => (img))) // Note the complete function body here .subscribe(url => { = url; });
Looking back, in order to save a few words, I spent a lot of time going to this pit, which is not worth it. There are many problems among them because I have written a lot of functional code, so I tend to express it concisely; but what is more worthy of being alert is that when facing uncertainty, I scold myself with a cliché - don't use tactical diligence to conceal strategic laziness.
We all know that experimentation is an efficient way to learn, but we must not bump into it or expect problems to disappear. We should follow the proven principles to the point and win with one blow. Remember to remember.
The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.