Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
264 views
in Technique[技术] by (71.8m points)

javascript - How to make multiple instances of my components not share @ViewChild maps/areas

I have a component that has a canvas on it, covered exactly by a mapped image. Based on mouse movement on top of the image, I highlight or unhighlight part of the canvas. I had intended to have multiple components like this, each independent from one another, one after another on my page, repeated vertically (so, the "upper" canvas is the one closer to the top of the page, whereas the "lower" canvas is the one after it)

The problem is that events on the bottom image map only affect the first canvas (i.e. if I mouseover the correct area on the second canvas, then the first canvas is the one that gets drawn on). I want it so that each component is independent - if I do something on the second component, it should affect the second canvas and not the first canvas.

In other words, Both components are sharing the first component's canvas, instead of each using their own canvases. This is a problem.

Here's an MCVE:

dummy-component.html:

<div style="position: relative; display: inline-block; width: 520px; height: 160px; background-color: green">
    <canvas #myCanvas width="520" height="160" 
      style="position: absolute; left: 0; top: 0; background-color: green; user-select: none"></canvas>
    <img #mapOverlay width="520" height="160" 
      style="position: absolute; left: 0; top: 0; background-color: transparent; user-select: none" 
      usemap="#overlay">
  
    <map #overlay id="overlay" name="overlay">
      <area shape="rect" coords="0, 0, 259, 159" 
        (mouseover)="hover()" (mouseout)="unhover()" (click)="drawSmiley()">
    </map>
</div>

dummy-component.ts:

@Component({
  selector: 'app-dummy',
  templateUrl: './dummy.component.html',
})
export class DummyComponent implements AfterViewInit {
  @ViewChild('myCanvas') private myCanvasElement!: ElementRef<HTMLCanvasElement>
  private myCanvas!: CanvasRenderingContext2D;
  private smileyFace: HTMLImageElement = new Image();

  constructor() { this.smileyFace.src = "assets/smiley.png"; }

  ngAfterViewInit(): void {
    this.myCanvas = <CanvasRenderingContext2D> this.myCanvasElement.nativeElement.getContext('2d');
  }

  hover() {
    this.myCanvas.clearRect(0, 0, 520, 160);
    this.myCanvas.fillStyle = "#ffffff33";  // draw transparent highlight
    this.myCanvas.fillRect(0, 0, 260, 160);
  }
  unhover() { this.myCanvas.clearRect(0, 0, 520, 160); }
  drawSmiley() { this.myCanvas.drawImage(this.smileyFace, 15, 15); }
}

app-component.html:

<div>
    <app-dummy></app-dummy>
    <app-dummy></app-dummy>
</div>

The first canvas's requisite events also affect the first canvas, fwiw.

I can't find any questions about this, or guides on how to accomplish this - Angular's documentation for @ViewChild says that "The change detector looks for the first element or the directive matching the selector in the view DOM", and that's probably related (I suspect the second element is borrowing the first element's @ViewChild and also using it for itself), but I don't know how to apply that information and make it stick to its own canvas.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

The issue appears to be with the maps, which all have the same name. When the image binds to a map, it chooses the first one in the entire DOM with the same name, which is happens to be the first component's map - and since functions and whatnot are bound to that first component, it appears as though everything's happening on the first component.

I feel like this is a bug, and that things like this should be scoped to only inside the component being activated, but it's probably intended behavior based on how the whole template is rendered.

Anyway, Solution: add a public id attribute to the DummyComponent, and inject it into both the name of the map and the useMap attribute of the image:

HTML:

<img #mapOverlay width="520" height="160" 
  style="position: absolute; left: 0; top: 0; background-color: transparent; user-select: none" 
  useMap="#{{name}}">

<map #overlay id="overlay" name="{{name}}">
  <area shape="rect" coords="0, 0, 259, 159" 
    (mouseover)="this.hover()" (mouseout)="this.unhover()" (click)="this.drawSmiley()">
</map>

Component:

let idGenerator = 0;

@Component({
  selector: 'app-dummy',
  templateUrl: './dummy.component.html',
})
export class DummyComponent implements AfterViewInit {
  public name: string;

  ...

  constructor() {
    this.smileyFace.src = "assets/smiley.png";
    this.name = "DummyComponent" + ++idGenerator;
  }
  ...
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
...