Angular组件多实例引用DOM元素时出现异常问题
问题描述
我开发了一个页面滚动时会显示图形的头部组件,单独使用时完全符合预期,但当创建该组件的多个实例(用于多个头部)时,所有实例都会继承第一个实例的状态。
推测这是因为我使用了GetElementById(),多实例场景下会出现多个拥有相同ID的div元素,同时我的样式绑定方式也可能引发类似问题。
请问如何让每个组件的脚本仅访问自身组件内的DOM元素,以及如何为这些元素动态设置样式?
app.component.html
<div class="columns mt-5 ml-5 mr-5"> <div class="column is-12"> <app-title></app-title> </div> </div> <div class="columns mt-5 ml-5 mr-5"> <div class="column is-12"> <app-title></app-title> </div> </div>
title.component.html
<div class="background"> <div class="item"> <span class="title titleMain">Unity Projects</span> <div [style]="polygonDivStyle"> <div class="titleDiv" id="unityTitle" [style]="unityClipPath"> <svg id="triangle" width="320" height="320"> <polygon points="155, 30 280, 280 30, 280" stroke-width="30" stroke="#FF6EFF" fill="none" /> </svg> </div> </div> </div> </div>
title.component.ts
import { Component, OnInit, HostListener} from '@angular/core'; @Component({ selector: 'app-title', templateUrl: './title.component.html', styleUrls: ['./title.component.css'] }) export class TitleComponent implements OnInit { contactClipPath!: string; contactTitle!: HTMLDivElement | null; unityClipPath!: string; unityTitle!: HTMLDivElement | null; polygonDivStyle!: string; constructor() { } ngOnInit(): void { this.unityClipPath = `-webkit-clip-path: polygon(0% 0%);`; this.unityTitle = document.getElementById('unityTitle') as HTMLDivElement| null this.RadialReveal(this.unityTitle, this.unityClipPath); this.polygonDivStyle = ""; } @HostListener('window:scroll', ['$event']) onScroll(event:Event){ this.RadialReveal(this.unityTitle, this.unityClipPath); } RadialReveal(clip:HTMLDivElement | null, clipPath:string) { let top = clip?.getBoundingClientRect().top; let scrollPercent = 0; if(top && visualViewport) scrollPercent= ((top / visualViewport.height) * 300 -100); if (clip != null && scrollPercent > 1 && scrollPercent < 100) { this.polygonDivStyle = ''; let scrollOpposite = 100 - scrollPercent; if(scrollOpposite >= 0 && scrollOpposite < 33) { clipPath = `-webkit-clip-path: polygon(0% `+[scrollPercent-67]+`% ,50% 33%, 0% 33%, 0% 66%, 100% 66%, 100% `+[scrollOpposite+66]+`%, 50% 66%, 0% 66%);`; } else if(scrollOpposite >= 33 && scrollOpposite < 66) { clipPath = `-webkit-clip-path: polygon(0% 0%, `+[(scrollOpposite-34)*3]+`% 0% ,50% 33%, 0% 33%, 0% 100%, `+[(scrollPercent-34)*3]+`% 100%, 50% 66%, 100% 66%, 100% 100%, 0% 100%);`; } else if(scrollOpposite >= 66 && scrollOpposite <= 100) { clipPath = `-webkit-clip-path: polygon(0% 0%, 100% 0% , 100% `+[33-scrollPercent]+`% ,50% 33%, 0% 33%, 0% `+[scrollPercent+66]+`%,50% 66%, 100% 66%, 100% 100%, 0% 100%);`; } } else if(scrollPercent > 100) { clipPath = `-webkit-clip-path: polygon(0% 0%);`; } else if(scrollPercent < 1) { clipPath = `-webkit-clip-path: polygon(0% 0%, 100% 0% , 100% 33% ,50% 33%, 0% 33%, 0% 66%,50% 66%, 100% 66%, 100% 100%, 0% 100%);`; this.polygonDivStyle = 'filter: drop-shadow(10px 10px 200px #ff00ff) drop-shadow(-10px -10px 50px #ff00ff);' } this.unityClipPath = clipPath; } }
解决方案
1. 用Angular视图查询替代全局DOM查询
全局document.getElementById会获取页面中第一个匹配ID的元素,导致多实例冲突。改用Angular的@ViewChild装饰器,精准获取当前组件实例内的DOM元素:
- 修改title.component.html:去掉
id,改用模板引用变量
<div class="titleDiv" #unityTitle [style]="unityClipPath"> <svg #triangle width="320" height="320"> <polygon points="155, 30 280, 280 30, 280" stroke-width="30" stroke="#FF6EFF" fill="none" /> </svg> </div>
- 修改title.component.ts:导入
ViewChild和ElementRef,获取当前组件内的元素
import { Component, OnInit, HostListener, ViewChild, ElementRef} from '@angular/core'; @Component({ selector: 'app-title', templateUrl: './title.component.html', styleUrls: ['./title.component.css'] }) export class TitleComponent implements OnInit { contactClipPath!: string; unityClipPath!: string; polygonDivStyle!: string; // 获取当前组件内的unityTitle元素 @ViewChild('unityTitle') unityTitle!: ElementRef<HTMLDivElement>; constructor() { } ngOnInit(): void { this.unityClipPath = `-webkit-clip-path: polygon(0% 0%);`; // 使用nativeElement访问DOM节点 this.RadialReveal(this.unityTitle.nativeElement, this.unityClipPath); this.polygonDivStyle = ""; } @HostListener('window:scroll', ['$event']) onScroll(event:Event){ this.RadialReveal(this.unityTitle.nativeElement, this.unityClipPath); } RadialReveal(clip:HTMLDivElement, clipPath:string) { let top = clip.getBoundingClientRect().top; let scrollPercent = 0; if(top && visualViewport) scrollPercent= ((top / visualViewport.height) * 300 -100); // 后续逻辑保持不变,clip不再为null if (scrollPercent > 1 && scrollPercent < 100) { this.polygonDivStyle = ''; let scrollOpposite = 100 - scrollPercent; if(scrollOpposite >= 0 && scrollOpposite < 33) { clipPath = `-webkit-clip-path: polygon(0% ${scrollPercent-67}% ,50% 33%, 0% 33%, 0% 66%, 100% 66%, 100% ${scrollOpposite+66}%, 50% 66%, 0% 66%);`; } else if(scrollOpposite >= 33 && scrollOpposite < 66) { clipPath = `-webkit-clip-path: polygon(0% 0%, ${(scrollOpposite-34)*3}% 0% ,50% 33%, 0% 33%, 0% 100%, ${(scrollPercent-34)*3}% 100%, 50% 66%, 100% 66%, 100% 100%, 0% 100%);`; } else if(scrollOpposite >= 66 && scrollOpposite <= 100) { clipPath = `-webkit-clip-path: polygon(0% 0%, 100% 0% , 100% ${33-scrollPercent}% ,50% 33%, 0% 33%, 0% ${scrollPercent+66}%,50% 66%, 100% 66%, 100% 100%, 0% 100%);`; } } else if(scrollPercent > 100) { clipPath = `-webkit-clip-path: polygon(0% 0%);`; } else if(scrollPercent < 1) { clipPath = `-webkit-clip-path: polygon(0% 0%, 100% 0% , 100% 33% ,50% 33%, 0% 33%, 0% 66%,50% 66%, 100% 66%, 100% 100%, 0% 100%);`; this.polygonDivStyle = 'filter: drop-shadow(10px 10px 200px #ff00ff) drop-shadow(-10px -10px 50px #ff00ff);' } this.unityClipPath = clipPath; } }
2. 优化样式绑定方式
将[style]的全量样式绑定改为针对单个属性的绑定,更清晰且避免冗余:
- 修改title.component.html:
<div class="titleDiv" #unityTitle [style.clip-path]="unityClipPathValue"> <svg #triangle width="320" height="320"> <polygon points="155, 30 280, 280 30, 280" stroke-width="30" stroke="#FF6EFF" fill="none" /> </svg> </div>
- 修改title.component.ts:直接存储
clip-path的值,Angular会自动处理浏览器前缀
// 替换原unityClipPath为unityClipPathValue unityClipPathValue!: string; ngOnInit(): void { this.unityClipPathValue = 'polygon(0% 0%)'; this.RadialReveal(this.unityTitle.nativeElement, this.unityClipPathValue); this.polygonDivStyle = ""; } // 在RadialReveal方法中,直接设置polygon值即可 clipPath = `polygon(0% ${scrollPercent-67}% ,50% 33%, 0% 33%, 0% 66%, 100% 66%, 100% ${scrollOpposite+66}%, 50% 66%, 0% 66%)`; // 其他分支同理,去掉-webkit-前缀
经过以上修改,每个组件实例会独立维护自己的DOM引用和样式状态,不会互相干扰。
内容的提问来源于stack exchange,提问作者Jimmy
相关产品推荐
相关产品推荐

