Angular实现svg和png图片下载实现

æç»å¸¸æèï¼å¨é¢ä¸´ä¸ä¸ªä¸ç¡®å®é®é¢æ¶ï¼ä»¥å¾çç»éªç©¶ç«ææ è¾å©ä½ç¨ï¼å¦ææç»éªéå¿ä¼äº§çä½ç§ç¨åº¦çå½±åï¼å¨ä¸ä¸æ±ç´¢æªæä¹åï¼å¦ä½æ¾åæ¾ç»çæè§ï¼æ°è¥çµåä¸ç°ï¼å¡æ­¤ç§ç§ï¼ç»æ¯è¦æèæ»ç»çï¼è¿ç¯æ章便æ¯æçåæä¹ä½ã

æ¬ç¯æç« ä¼è®°è¿°ä¸äºå®ç¨çsvgä¸pngä¹é´ç转æ¢æ巧并强è°ä¸ç§æèååã

æ¦è¿°

æå·§

  1. svgåpngå¾ç转æ¢åä¸è½½
  2. 解å³chrome data url too largeä¸è½½é®é¢
  3. 解å³@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ä¹åå¼å¥ç语æ³ï¼å®çå¨ç§°æ¯ Template reference variable (#var) ï¼åè½å¨äºå¼ç¨å¶ææåç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çDOM interface. æ¯å¦è¯´æ们è¦è·å <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> .

function loadImage(url: string): Observable<HTMLImageElement> {
 const result = new Subject<HTMLImageElement>();
 const image = document.createElement('img');
 image.src = url;
 image.addEventListener('load', () => {
  result.next(image);
 });
 return result.asObservable();
}

第ä¸æ­¥æ¯å° <img> 转æ¢æcanvas.

private toPngDataURL(img: HTMLImageElement): string {
 const canvas = document.createElement('canvas');
 canvas.width = img.width;
 canvas.height = img.height;
 canvas.getContext('2d').drawImage(img, 0, 0);
 return canvas.toDataURL('image/png');
}

canvas转æpngå¾çå°±æ¯ä¸è¿°ä¸å¥ toDataURL çè°ç¨ã

3. å¾çä¸è½½

ä¸é¢çä¸ä¸ªæ­¥éª¤å¯ä»¥åèµ·æ¥ã

private generateDownloadUrl() {
 const svgDataURL = this.toSvgDataURL(this.template.svgRef.nativeElement);
 loadImage(svgDataURL)
 .pipe(map(this.toPngDataURL))
 .subscribe(url => {
  this.pngUrl = url;
  this.svgUrl = svgDataURL;
 });
}

<a> åç´ ç href å±æ§æ¯å¯ä»¥æ¥åDataURLçï¼æ以æ们æsvg dataURLåpng dataURLèµå¼ç»æååépngUrlä¸svgUrlå³å¯ï¼æåæ æ³¨downloadå±æ§è¡¨ç¤ºè¿æ¯ä¸æ¡ä¸è½½é¾æ¥ã

<a [href]="svgUrl" target="_blank" download="template.svg">ä¸è½½ SVG çæ¬</a>
<a [href]="pngUrl" target="_blank" download="template.png">ä¸è½½ PNG çæ¬</a>

解å³chrome data url too largeä¸è½½é®é¢

ä¸è¿°è¿ç¨çä¸å»é¡ºå©æµçï¼ä½æ¯äºå®ä¸ä¸æ¦å¾çè¿å¤§ï¼å¨ä¸è½½æ¶ï¼chromeæµè§å¨ä¼æåºç½ç»é误ãè¿æ¯chrome/chormiumåæ ¸å­å¨å·²ä¹çbugï¼stackoverflowä¸ç»åºçç»è¡æ¹æ¡æ¯ç¨ URL.createObjectURL(blob) åè代ä¹ã

private toSvg(viewerSvg: SVGSVGElement): string {
 const svg = viewerSvg.cloneNode(true) as SVGSVGElement;
 svg.setAttribute('width', '600px');
 const blob = new Blob([svg.outerHTML], {type: 'image/svg+xml'});
 const url = URL.createObjectURL(blob);
 return url;
}

对äºpngçå¤çä¹å¯ä»¥å¾çµæ´»ã

private toPng(img: HTMLImageElement): Observable<string> {
 const canvas = document.createElement('canvas');
 canvas.width = img.width;
 canvas.height = img.height;
 canvas.getContext('2d').drawImage(img, 0, 0);
 const result = new Subject<string>();
 canvas.toBlob(blob => {
  const url = URL.createObjectURL(blob);
  result.next(url);
 });

 return result.asObservable();
}

ä¸è¿ï¼å ä¸ºæµè§å¨çå®å¨è­¦åï¼urléè¦ç»è¿sanitizeæè½æ¾è¡ãè¿å¨Angularéå¯ä»¥å¯¼å¥DomSanitizerå¤çã

import {DomSanitizer, SafeResourceUrl} from '@angular/platform-browser';

... 
 constructor(private sanitizer: DomSanitizer) {
 }

åæ¥ç代ç å¾è¿åSafeResourceUrl.

private toSvg(viewerSvg: SVGSVGElement): SafeResourceUrl {
 const svg = viewerSvg.cloneNode(true) as SVGSVGElement;
 svg.setAttribute('width', '600px');
 const blob = new Blob([svg.outerHTML], {type: 'image/svg+xml'});
 const url = URL.createObjectURL(blob);
 const safeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(url);
 return safeUrl;
}
private toPng(img: HTMLImageElement): Observable<SafeResourceUrl> {
 const canvas = document.createElement('canvas');
 canvas.width = img.width;
 canvas.height = img.height;
 canvas.getContext('2d').drawImage(img, 0, 0);
 const result = new Subject<SafeResourceUrl>();
 canvas.toBlob(blob => {
  const url = this.sanitizer.bypassSecurityTrustResourceUrl(URL.createObjectURL(blob));
  result.next(url);
 });

 return result.asObservable();
}

åæ¥çå并æä½ç¸åºä¿®æ¹ã

private generateDownloadUrl() {
 this.svgUrl = this.toSvg(this.template.svgRef.nativeElement);
 const svgDataURL = this.toSvgDataURL(this.template.svgRef.nativeElement);
 loadImage(svgDataUrl)
 .pipe(flatMap(this.toPng)) // æ­¤å¤æå
 .subscribe(url => {
  this.pngUrl = url;
 });
}

å¼å¾æ³¨æçæ¯åæ¥çpipe map æ¹æäº flatMap ï¼å ä¸º toPng è¿åè¿æ¯ä¸ä¸ªObservableï¼èä¸æ¯ç®åçå¼ã

è¿æ ·çä¸å»æ¯æ²¡æé®é¢çï¼ä½æ¯å¦ä¸é¢è¿æ®µä»£ç ç注éï¼ æ­¤å¤æå ãåå¨åªéï¼ç¨åæä¼å¨ååå¤ä½æ·±å¥æ¢è®¨ï¼ç°å¨æä¸æç½®ï¼è¿å¥ä¸ä¸ä¸ªææ¯è¯é¢ã

解å³@ViewChildæªåæ¶å·æ°é®é¢

@ViewChildåå¾é¡µé¢åç´ å¯è½ä¸æ¯ææ°çï¼AngularçChange detectionéè¦æ¶é´å®æå·æ°ï¼æ以æå¾ç­æ¶é´ç延è¿ãè¿å¯¹äºæçç¨åºèè¨æ¯ä¸è½å®¹å¿çã延è¿è½ä¸è½å®¹å¿ï¼ä½æ¯ç­å¾å·æ°ä¹ååå¤çå¾çè¿æ¯å¯ä»¥çï¼æ以解å³æ¹æ¡å°±æ¯ç­å¾ä¸ç§éååå¾ç转æ¢ã

private waitForViewChildReady() {
 return new Promise<string>((resolve) => {
  const wait = setTimeout(() => {
   clearTimeout(wait);
   resolve('workaround!');
  }, 1000);
 });
}

ç»ç« ç¨åºè°ç¨å¦ä¸ã

this.waitForViewChildReady()
.then(this.generateDownloadUrl())
.catch(err => console.error(err))

åå

ååæ¯ç¨æ¥æ导å®è·µçã

æ°¸è¿ä»é®é¢æè¿çå°æ¹å¼å§åæ

ä¸è¦ç¨ææ¯ä¸çå¤å¥æ©é¥°æç¥ä¸çææ°

æ个人对Angular并ä¸ååçæï¼å¨å®ç°svgåpngå¾çä¸è½½åè½çè¿ç¨ä¸­éå°ä¸äºåï¼è¿äºåææ·±ææµï¼æ·±çç´æ¥é¢åstackoverflowç¼ç¨ç»è¿ï¼æµçé ä¸ªäººè½å解å³ãåªä¸è¿ï¼å¯¹è§£å³è¿äºæµåçè¿åº¦èªä¿¡å´è®©æçæç»´é·å¥ææ°ï¼å¯¼è´äºé¿æ¶é´ç浪费ã

è¿éçæµåå°±æ¯Javascriptè­åæ­èçthis scopeé®é¢ã

å顾ä¸ä¸ä¸é¢æåç代ç ï¼

loadImage(svgDataUrl)
 .pipe(flatMap(this.toPng)) // æ­¤å¤æå
 .subscribe(url => {
  this.pngUrl = url;
 });

toPng ç代ç å¦ä¸ï¼

private toPng(img: HTMLImageElement): Observable<SafeResourceUrl> {
 const canvas = document.createElement('canvas');
 canvas.width = img.width;
 canvas.height = img.height;
 canvas.getContext('2d').drawImage(img, 0, 0);
 const result = new Subject<SafeResourceUrl>();
 canvas.toBlob(blob => {
  const url = this.sanitizer.bypassSecurityTrustResourceUrl(URL.createObjectURL(blob));
  result.next(url);
 });

 return result.asObservable();
}

ç¨åºè¿è¡æ¶ï¼æåºäºä¸ä¸ªé误 cannot read bypassSecurityTrustResourceUrl of undefined.

第ä¸ååºæ¯ææ¯ä¸æ¯åéäºåéåï¼åä¸éªè¯ä¹ååç°æ²¡æåéãç¶èè¿ä¸æ­¥å¶å®å®å¨æ²¡å¿è¦ï¼åå å¨äºè¿äºåéé½æ¯ç¼è¾å¨è¾å©è¡¥å¨çã

ç´§æ¥çï¼æå¨ toBlob æ¹æ³æå¥äº console.log(this.sanitizer) ï¼è¿è¡åæå°çç»ææ¯ undefined ãè¿è½è¯´æä»ä¹ï¼ç¨åºæ§è¡å°è¿éäºï¼å¶å®è¿ç§åæ³ä¹æ²¡å¿è¦ï¼å ä¸ºæ§å¶å°çé误信æ¯æ确表æè¿æ®µä»£ç æ§è¡å°äºï¼å¹¶ä¸åºéäºã

ç¶åï¼æå¼å§æèâé¾éæåçAngularç注å¥æ¹å¼ä¸å¯¹ï¼âï¼å¨é寻Angularçå®æ¹ææ¡£åæ ·ä¾ä¹åï¼æ确信注å¥æ¹å¼æ²¡æé®é¢ãè¿æ­¥æå¯åä¹å¤ï¼å ä¸ºå¯¹Angularæ¬èº«ä¸å¤çæï¼æ¥ææ¡£æ¯åççè¡ä¸ºï¼ä½æ¯è§£å³æ路离ç®æ å¤ªè¿ï¼ç¨åºçé®é¢åºè¯¥éè¿debug解å³ã

æ å¥ä¹ä¸ï¼æå¼å§æçåä¾èµä¸è½½åºç°é®é¢ï¼æ以ç¨äºææè ¢çæ¹æ³ï¼å é¤ node_modules ï¼ç¶åéæ°ä¸è½½å¨é¨ä¾èµãè¿æ¯ä¸æ­¥èæ¶çæä½ï¼æ大ç浪费就åçå¨è¿éãææåæ¥å¯¹äºæ¢ç´¢é®é¢æ»ç»çåºæ¬åå åæå¾ä»æè¿çè·¯å¼å§ å¿å¾ä¸å¹²äºåãå°è¯æ æä¹åï¼æ没æä»çè§å°ä¸­è·³åºæ¥ï¼éå¿äº è±æ¶é´æ¾ç©ºèªå·± ååï¼è¿æ¯æ续纠ç»ï¼ç´è³æåæ¾å¼ã

第äºå¤©æ©ä¸ï¼åäºæ¯åå¡ï¼èè¢æ¸éäºäºãå¨ toPng æ¹æ³å¤ï¼ææå¥ console.log(this.sanitizer) ï¼åç°è¿ä¸ªå¯¹è±¡å®å¥½å°åºç°å¨å½ä»¤è¡ä¸­ï¼æ­¤å»çªç¶çµæä¸ç°ï¼åå¿èµ·å å¹´ååè¿ä¸ç¯å³äºJavascriptä½ç¨åçæç« ï¼å¯ä¸å°±æ¯thisæéçé®é¢ä¹ï¼

loadImage(svgDataUrl)
 .pipe(flatMap(this.toPng.bind(this))) // 注ææ­¤å¤bind(this)
 .subscribe(url => {
  this.pngUrl = url;
 });

æä»¥ç¨ bind(this) éå®thisçæåï¼ç¶ååç°ç¨åºè¿è¡æ­£å¸¸ï¼ä¸åå°±é½è±ç¶å¼æäºãå¼å¾ä¸æçæ¯ï¼è¿åªæ¯æ便å®çä¿®å¤ï¼å¶å®æ´å¯åçåæ³æ¯åå¨å½æ°ä½ã

loadImage(svgDataUrl)
 .pipe(flatMap(img => this.toPng(img))) // 注ææ­¤å¤å®æ´å½æ°ä½
 .subscribe(url => {
  this.pngUrl = url;
 });

åæ³èµ·æ¥ï¼ä¸ºäºèçå ä¸ªåè¯ï¼æèè´¹äºå¥½å¤æ¶é´å»è¶è¿ä¸ªåï¼è¿æ¯ä¸å¼å½çãè¿å¶ä¸­çé®é¢ä¸ä¹å ä¸ºæåè¿å¾å¤å½æ°å¼ä»£ç ï¼æ以å¾åç®æ´ç表达ï¼ä½æ¯æ´å¼å¾è­¦éçæ¯ï¼å¨é¢ä¸´ä¸ç¡®å®æ§é®é¢æ¶ææ°çæç»´æ¹å¼ï¼ç¨ä¸å¥å¥è¯è®­æ¥èªå·±ââä¸è¦ç¨ææ¯ä¸çå¤å¥æ©é¥°æç¥ä¸çææ°ã

æ们é½ç¥éè¯éªæ¯å­¦ä¹ çé«ææ¹å¼ï¼ä½æ¯åä¸å¯ä¹±ç¢°ä¹±æãæå¾é®é¢ä¸ç¿¼èé£ï¼æ们åºå½éµå¾ªç»è¿éªè¯çååå中è¦å®³ãä¸å»å¶èï¼åè®°åè®°ã

以ä¸å°±æ¯æ¬æçå¨é¨å容ï¼å¸æ对大家ç学习ææ帮å©ï¼ä¹å¸æ大家å¤å¤æ¯æèæ¬ä¹å®¶ã

相关推荐