首页 > 解决方案 > Stencil js - requestAnimationFrame 不会出现在所有组件中

问题描述

我使用 stencil.js 创建了一个简单的目录项组件。在组件中有画布标签,我在上面绘制了动画曲线。在 componentDidLoad 函数中,我定义了画布,对其进行初始化并调用 animate 函数。这是组件本身的代码:

import { Component, Host, h, Prop, Element  } from "@stencil/core";
import { Item } from "../hotb-catalog-container/data";
import { initCanvas, init, animate } from "./wave.js";
import axios from "axios";

@Component({
  tag: "hotb-catalog-item",
  styleUrl: "hotb-catalog-item.scss",
  shadow: false,
})
export class HotbCatalogItem {
  @Prop() item: Item;
  @Element() el: HTMLElement;

  // @Event({ bubbles: true, composed: true }) itemSelect: EventEmitter<Item>;

  itemSelected(event: Event) {
    event.preventDefault();
    //sessionStorage.setItem("item", JSON.stringify(this.item));
    axios.post("/item", { item: this.item }).then(() => {
      window.location.href = "http://localhost:3000/item";
    });
  }

  componentDidLoad() {
    let canvas = this.el.querySelector('canvas');
    initCanvas(canvas);
    init();
    animate();
  }

  render() {
    return (
      <Host>
        <div class="heading">
          <h1>{this.item.name}</h1>
          <span> מ{this.item.origin} &#183; {this.item.taste}</span>
        </div>
        <div class="center-part">
          <div class="center-part__img"></div>
          <div class="center-part__icon center-part__icon--temp">
            {this.item.temp}&deg;C
          </div>
          <div class="center-part__icon center-part__icon--time">
            {this.item.time}
          </div>
        </div>
        <a
          href="/item"
          onClick={this.itemSelected.bind(this)}
          class="primary-btn homepage__tea"
        >
          לצפיה
        </a>
        <canvas></canvas>
      </Host>
    );
  }
}

如您所见,我导入了画布代码,这里是:

let line1;
let line2;
var ctx;
var canvasElem;

function initCanvas(canvas) {
  canvasElem = canvas;
  ctx = canvas.getContext("2d");
  const parent = canvas.parentElement;
  canvas.width = parent.clientWidth;
  canvas.height = parent.scrollHeight;
}

class LineGen {
  constructor(x, y, directionY, cpX, directionCPX, cpY, directionCPY, size, color) {
    this.x = x;
    this.y = y;
    this.directionY = directionY;
    this.cpX = cpX;
    this.directionCPX = directionCPX;
    this.cpY = cpY;
    this.directionCPY = directionCPY;
    this.size = size;
    this.color = color;
  }

  draw() {
    ctx.beginPath();
    ctx.moveTo(this.x, this.y);
    ctx.quadraticCurveTo(this.cpX, this.cpY, 400, this.y);
    ctx.strokeStyle = this.color;
    ctx.lineWidth = this.size;
    ctx.stroke();
  }

  update() {
    if (this.y > 490 || this.y < 400) {
      this.directionY = -this.directionY;
    }

    if (this.color === '#E07D31') {
      if (this.cpX > 390 || this.cpX < 10) {
        this.directionCPX = -this.directionCPX;
      }
      if (this.cpY > 590 || this.cpY < 150) {
        this.directionCPY = -this.directionCPY;
      }
    } else if (this.color === '#49E048') {
      if (this.cpX > 390 || this.cpX < 10) {
        this.directionCPX = -this.directionCPX;
      }
      if (this.cpY > 560 || this.cpY < 240) {
        this.directionCPY = -this.directionCPY;
      }
    }

    //this.y += this.directionY;
    this.cpX += this.directionCPX;
    this.cpY += this.directionCPY;
    this.draw();
  }
}

class Line extends LineGen {
  constructor(x, y, directionY, cpX, directionCPX, cpY, directionCPY, size, color) {
    super(x, y, directionY, cpX, directionCPX, cpY, directionCPY, size, color);
  }
}

function init() {
  let x = 0;
  let y = 400;
  let y2 = 380;

  let directionY = -.2;
  let cpX = 100;
  let cpX2 = 100;
  let directionCPX = .15;

  let cpY = 300;
  let cpY2 = 300;
  let directionCPY = .15;
  let directionCPY2 = .178125;

  let size = 2;

  let color = '#E07D31';
  let color2 = '#49E048';

  line1 = new Line(x, y, directionY, cpX, directionCPX, cpY, directionCPY2, size, color);
  line2 = new Line(x, y2, directionY, cpX, directionCPX, cpY, directionCPY, size, color2);
}

function animate() {
  requestAnimationFrame(animate);
  ctx.clearRect(0, 0, canvasElem.width, canvasElem.height);

  line1.update();
  line2.update();
}

export { initCanvas, init, animate };

现在,目录项组件位于容器组件内部,如下所示:

    let items;
    if (this.newItemsArr.length == 0) {
      items = <div class="no-items">אין משקאות שתואמים לחיפוש שלך</div>;
    } else {
      items = this.newItemsArr.map((item) => (
        <hotb-catalog-item item={item}></hotb-catalog-item>
      ));
    }
    return (
      <Host>
        {items}
      </Host>
    );

最终结果是组件显示画布和线条,但动画仅在其中一个中发生。其余的在初始状态下是静态的。请告诉我为什么会发生这种情况以及我可以做些什么来修复它,以便所有组件都可以动画。重要的是要注意,当我刷新代码并且浏览器通过热重载刷新时,另一个组件会在每次刷新时启动动画等等。

谢谢您的帮助。

标签: javascriptcanvascomponentsrequestanimationframestenciljs

解决方案


问题是某些变量是在函数外部定义的,initCanvas因此在所有组件(line1line2和)之间共享。因此,每次您调用它们时,它们都会被覆盖。ctxcanvasEleminitCanvas

一个快速的解决方案是将它包装在一个类中:

export class WaveCanvas {
  constructor(canvas) {
    this.canvasElem = canvas;
    this.ctx = canvas.getContext("2d");
    const parent = canvas.parentElement;
    canvas.width = parent.clientWidth;
    canvas.height = parent.scrollHeight;
  }

  init() {
    // ...
    this.line1 = new Line(...);
  }

  animate() {
    requestAnimationFrame(() => this.animate());
    this.ctx.clearRect(0, 0, this.canvasElem.width, this.canvasElem.height);

    this.line1.update();
    this.line2.update();
  }
}

然后在组件中实例化它:

  componentDidLoad() {
    let canvas = this.el.querySelector('canvas');
    const waveCanvas = new WaveCanvas(canvas);
    waveCanvas.init();
    waveCanvas.animate();
  }

这样每个实例都WaveCanvas将持有自己对正确<canvas>元素的引用。


推荐阅读