Angular项目中部分Google Material Icons无法在浏览器显示
Angular自定义Material图标库部分图标无法渲染问题排查
我正在为Angular项目开发自定义图标库,由于Font Awesome多数图标不再免费,选择采用Google Material Icons并通过样式表集成,同时创建了IconService和IconDirective来简化图标调用。但实际运行时,部分图标(如progress_activity)无法在浏览器中正常渲染,已尝试更换浏览器、调整CSS字体设置等方法,问题仍未解决。
相关代码
icon.service.ts
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { map } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class IconsService { constructor(private http: HttpClient, private sanitizer: DomSanitizer) {} getIcon(name: string): Observable<SafeHtml> { return this.http.get(`assets/icons/${name}.svg`, { responseType: 'text' }).pipe( map(svgString => this.sanitizer.bypassSecurityTrustHtml(svgString) as SafeHtml) // assert non-null ); } }
IconDirective
import {Directive, ElementRef, Input, Renderer2} from '@angular/core'; @Directive({ selector: '[icon]', standalone: true, }) export class IconDirective { private _iconName: string = ''; private _spin: boolean = false; private _size: string | undefined; private _flipHorizontal: boolean = false; private _flipVertical: boolean = false; private _color: string | undefined; @Input('icon') set icon(value: string) { const parts = value.split(' '); this._iconName = parts[0]; this._spin = parts.includes('spin'); this._flipHorizontal = parts.includes('flip-horizontal'); this._flipVertical = parts.includes('flip-vertical'); this._size = parts.find(part => part.match(/^[0-5]x$/)) || '1x'; // Default to 1x const colorPart = parts.find(part => part.startsWith('color-')); this._color = colorPart ? colorPart.split('-')[1] : undefined; this.updateIcon(); } constructor(private el: ElementRef, private renderer: Renderer2) { } ngAfterViewInit(): void { this.setupLazyLoading(); } private setupLazyLoading(): void { const options = { root: null, // observes changes in the viewport threshold: 0.01, // trigger when 1% of the element is visible }; const observer = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { this.updateIcon(); observer.unobserve(entry.target); // Stop observing once loaded } }); }, options); observer.observe(this.el.nativeElement); } private updateIcon(): void { console.log('Updating icon'); // Clear previous classes this.el.nativeElement.className = ''; // Set text content to the icon name this.renderer.setProperty(this.el.nativeElement, 'textContent', this._iconName); this.renderer.addClass(this.el.nativeElement, 'material-icons'); // Apply size, spin, and flip classes if (this._spin) { this.renderer.addClass(this.el.nativeElement, 'spin'); } if (this._flipHorizontal) { this.renderer.addClass(this.el.nativeElement, 'flip-horizontal'); } if (this._flipVertical) { this.renderer.addClass(this.el.nativeElement, 'flip-vertical'); } if (this._size) { this.renderer.addClass(this.el.nativeElement, `icon-${this._size}`); } if (this._color) { this.renderer.setStyle(this.el.nativeElement, 'color', this._color); } } }
HTML代码
<h1>Icon examples</h1> <h2>Sizing: <i icon="search"></i><i icon="search 2x"></i><i icon="search 3x"></i></h2> <h2>Spinning: <i icon="search spin"></i></h2> <h2>Flipping horizontally and vertically: <i icon="search flip-horizontal"></i><i icon="search flip-vertical"></i></h2> <h2>Colors: <i icon="search color-blue"></i><i icon="search color-red"></i><i icon="search color-green"></i></h2> <h1>Button example: <button><i icon="donut_large spin"></i>Loading</button></h1> <button><i icon="settings 2x"></i></button> <button><i icon="menu 2x"></i></button> <!--<button><i icon="stacks 2x"></i></button>--> <button><i icon="print 2x"></i></button> <button><i icon="open_with 2x"></i></button> <button><i icon="add 2x"></i></button> <button><i icon="public 2x"></i></button> <!--<button><i icon="captive_portal 2x"></i></button>--> <button><i icon="filter_alt 2x"></i></button> <button><i icon="filter_alt_off 2x"></i></button> <button><i icon="arrow_forward_ios 2x"></i></button> <button><i icon="arrow_back_ios 2x"></i></button> <button><i icon="pan_tool 2x"></i></button> <button><i icon="progress_activity 2x"></i></button>
CSS样式
/*Icon base styles */ [icon] { display: inline-block; font-size: 16px; padding: 2px; fill-opacity: 0; vertical-align: middle; horiz-align: center; } @font-face { font-family: 'Material Icons'; font-style: normal; font-weight: 400; src: url(https://example.com/MaterialIcons-Regular.eot); /* For IE6-8 */ src: local('Material Icons'), local('MaterialIcons-Regular'), url(https://example.com/MaterialIcons-Regular.woff2) format('woff2'), url(https://example.com/MaterialIcons-Regular.woff) format('woff'), url(https://example.com/MaterialIcons-Regular.ttf) format('truetype'); } /* Icon size variations */ @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } .spin { animation: spin 2s infinite linear; padding: 2px; } .flip-horizontal { transform: scaleX(-1); } .flip-vertical { transform: scaleY(-1); } .flip-left { transform: rotateY(180deg); } .flip-right { transform: rotateY(-180deg); } .material-icons.icon-1x { font-size: 16px; } /* Default Material Icons size */ .material-icons.icon-2x { font-size: 32px; } .material-icons.icon-3x { font-size: 48px; } .material-icons.icon-4x { font-size: 64px; }
问题排查与解决方案
1. 图标名称不匹配官方规范
progress_activity并非Google Material Icons官方提供的字体图标名称,先去官方图标库确认对应功能的正确图标名(比如progress_bar、circular_progress等),或者检查是否存在拼写错误。
2. 字体文件加载失败
CSS中@font-face使用的是占位地址https://example.com,导致字体文件无法正常加载。替换为有效的CDN地址或本地字体路径:
@font-face { font-family: 'Material Icons'; font-style: normal; font-weight: 400; src: url(https://fonts.gstatic.com/s/materialicons/v140/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2) format('woff2'); }
如果用本地字体,确保路径正确,比如url('assets/fonts/MaterialIcons-Regular.woff2')。
3. 指令未正确使用IconService
你创建了IconsService用于加载SVG图标,但IconDirective的updateIcon方法只是把图标名设为文本内容,实际是按字体图标逻辑实现的。如果要使用SVG,修改updateIcon并注入服务:
// 先在构造函数注入IconsService constructor(private el: ElementRef, private renderer: Renderer2, private iconsService: IconsService) {} private updateIcon(): void { console.log('Updating icon'); this.el.nativeElement.innerHTML = ''; this.el.nativeElement.className = ''; this.iconsService.getIcon(this._iconName).subscribe(svg => { this.el.nativeElement.innerHTML = svg; // 给SVG元素添加样式类 const svgEl = this.el.nativeElement.firstChild; if (this._spin) this.renderer.addClass(svgEl, 'spin'); if (this._size) this.renderer.addClass(svgEl, `icon-${this._size}`); // ...其他样式逻辑 }); }
4. CSS属性冲突
基础样式中设置了fill-opacity: 0,如果是SVG图标会直接透明,移除该属性,或针对SVG单独设置:
[icon] { display: inline-block; font-size: 16px; padding: 2px; vertical-align: middle; /* 移除 fill-opacity: 0; */ } [icon] svg { fill: currentColor; }
5. 懒加载重复触发问题
@Input设置时和懒加载触发时会重复调用updateIcon,添加标志位避免重复操作:
private isLoaded = false; private setupLazyLoading(): void { const options = { root: null, threshold: 0.01 }; const observer = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting && !this.isLoaded) { this.updateIcon(); this.isLoaded = true; observer.unobserve(entry.target); } }); }, options); observer.observe(this.el.nativeElement); }
内容的提问来源于stack exchange,提问作者swdev
相关产品推荐
相关产品推荐

