Vue.js 自定义组件渲染器

虽然在大多数 Web 应用程序中,您可能会将其渲染到 DOM,但有一些特殊情况下,您可能希望使用 Vue 用于其他用途。 假设您正在使用 WebGL 开发应用程序,您希望将其描述为 Vue 作为组件树。

我们将使用 pixi.js为本指南,所以请确保通过npm安装它. 这是一个先进的文章,因此,假定你已经有一个骨骼应用程序准备与webpack和View 2.2+. 此外,解释将主要通过代码评论由于组件的复杂性发生。

目标将是制作一组三个Vue组件,渲染器,容器和一个sprite组件,以便使用pixi.js在2D WebGL画布中绘制纹理。

最终的结果应该看起来像这样:

Vue.js logo rendered in PIXI.js

注意:这不是在Vue中实现完整PIXI渲染器的指南,只是基本知识,如果你打算做一些更严肃的事情,你将不得不自己处理任何事情。

返回器组件

这是初始化我们的PIXI阶段的组件,并为其所有后代提供PIXI对象(通过Vue 2.2+的 [提供 / 注射](/vuejs / upgrading-vue-2.2/#provide - 注射)系统)。

 1[label PIXIRenderer.vue]
 2<template>
 3  <div class="pixi-renderer">
 4    <canvas ref="renderCanvas"></canvas>
 5    <!-- All child <template> elements get added in here -->
 6    <slot></slot>
 7  </div>
 8</template>
 9<script>
10import Vue from 'vue';
11import * as PIXI from 'pixi.js';
12
13export default {
14  data() {
15    return {
16      // These need to be contained in an object because providers are not reactive.
17      PIXIWrapper: {
18        // Expose PIXI and the created app to all descendants.
19        PIXI,
20        PIXIApp: null,
21      },
22      // Expose the event bus to all descendants so they can listen for the app-ready event.
23      EventBus: new Vue()
24    }
25  },
26  // Allows descendants to inject everything.
27  provide() {
28    return {
29      PIXIWrapper: this.PIXIWrapper,
30      EventBus: this.EventBus
31    }
32  },
33
34  mounted() {
35    // Determine the width and height of the renderer wrapper element.
36    const renderCanvas = this.$refs.renderCanvas;
37    const w = renderCanvas.offsetWidth;
38    const h = renderCanvas.offsetHeight;
39
40    // Create a new PIXI app.
41    this.PIXIWrapper.PIXIApp = new PIXI.Application(w, h, {
42      view: renderCanvas,
43      backgroundColor: 0x1099bb
44    });
45
46    this.EventBus.$emit('ready');
47  }
48}
49</script>
50
51<style scoped>
52canvas {
53  width: 100%;
54  height: 100%;
55}
56</style>

这个组件主要做两件事。

1.当 renderer 被添加到 DOM 时,在画布上创建一个新的 PIXI 应用程序并发出已准备的事件 2. 向所有后代组件提供 PIXI 应用程序和事件总线

集装箱组件

容器组件可以包含任意数量的螺旋或其他容器,允许组巢。

 1[label PIXIContainer.vue]
 2<script>
 3export default {
 4  // Inject the EventBus and PIXIWrapper objects from the ancestor renderer component.
 5  inject: ['EventBus', 'PIXIWrapper'],
 6  // Take properties for the x and y position. (Basic, no validation)
 7  props: ['x', 'y'],
 8
 9  data() {
10    return {
11      // Keep a reference to the container so children can be added to it.
12      container: null
13    }
14  },
15
16  // At the current time, Vue does not allow empty components to be created without a DOM element if they have children.
17  // To work around this, we create a tiny render function that renders to <template><!-- children --></template>.
18  render: function(h) {
19    return h('template', this.$slots.default)
20  },
21
22  created() {
23    // Create a new PIXI container and set some default values on it.
24    this.container = new this.PIXIWrapper.PIXI.Container();
25
26    // You should probably use computed properties to set the position instead.
27    this.container.x = this.x || 0;
28    this.container.y = this.y || 0;
29
30    // Allow the container to be interacted with.
31    this.container.interactive = true;
32
33    // Forward PIXI's pointerdown event through Vue.
34    this.container.on('pointerdown', () => this.$emit('pointerdown', this.container));
35
36    // Once the PIXI app in the renderer component is ready, add this container to its parent.
37    this.EventBus.$on('ready', () => {
38      if (this.$parent.container) {
39        // If the parent is another container, add to it.
40        this.$parent.container.addChild(this.container)
41      } else {
42        // Otherwise it's a direct descendant of the renderer stage.
43        this.PIXIWrapper.PIXIApp.stage.addChild(this.container)
44      }
45
46      // Emit a Vue event on every tick with the container and tick delta for an easy way to do frame-by-frame animation.
47      // (Not performant)
48      this.PIXIWrapper.PIXIApp.ticker.add(delta => this.$emit('tick', this.container, delta))
49    })
50  }
51}
52</script>

容器组件采取两个属性,x和y,为位置,并发出两个事件,指向下和点击来处理点击和框架更新,分别。

Sprite 组件

Sprite 组件几乎与容器组件相同,但对 PIXI 的 Sprite API 有一些额外调整。

 1[label PIXISprite.vue]
 2<script>
 3export default {
 4  inject: ['EventBus', 'PIXIWrapper'],
 5  // x, y define the sprite's position in the parent.
 6  // imagePath is the path to the image on the server to render as the sprite.
 7  props: ['x', 'y', 'imagePath'],
 8
 9  data() {
10    return {
11      sprite: null
12    }
13  },
14
15  render(h) { return h() },
16
17  created() {
18    this.sprite = this.PIXIWrapper.PIXI.Sprite.fromImage(this.imagePath);
19    // Set the initial position.
20    this.sprite.x = this.x || 0;
21    this.sprite.y = this.y || 0;
22    this.sprite.anchor.set(0.5);
23
24    // Opt-in to interactivity.
25    this.sprite.interactive = true;
26
27    // Forward the pointerdown event.
28    this.sprite.on('pointerdown', () => this.$emit('pointerdown', this.sprite));
29    // When the PIXI renderer starts.
30    this.EventBus.$on('ready', () => {
31      // Add the sprite to the parent container or the root app stage.
32      if (this.$parent.container) {
33        this.$parent.container.addChild(this.sprite);
34      } else {
35        this.PIXIWrapper.PIXIApp.stage.addChild(this.sprite);
36      }
37
38      // Emit an event for this sprite on every tick.
39      // Great way to kill performance.
40      this.PIXIWrapper.PIXIApp.ticker.add(delta => this.$emit('tick', this.sprite, delta));
41    })
42  }
43}
44</script>

Sprite 与容器大致相同,但它有一个 imagePath 插件,允许您选择从服务器上载和显示的图像。

使用

一个简单的Vue应用程序,使用这三个组件来渲染文章开头的图像:

 1[label App.vue]
 2<template>
 3  <div id="app">
 4    <pixi-renderer>
 5      <container
 6        :x="200" :y="400"
 7        @tick="tickInfo" @pointerdown="scaleObject"
 8      >
 9        <sprite :x="0" :y="0" imagePath="./assets/vue-logo.png"/>
10      </container>
11    </pixi-renderer>
12  </div>
13</template>
14
15<script>
16import PixiRenderer from './PIXIRenderer.vue'
17import Sprite from './PIXISprite.vue'
18import Container from './PIXIContainer.vue'
19
20export default {
21  components: {
22    PixiRenderer,
23    Sprite,
24    Container
25  },
26
27  methods: {
28    scaleObject(container) {
29      container.scale.x *= 1.25;
30      container.scale.y *= 1.25;
31    },
32
33    tickInfo(container, delta) {
34      console.log(`Tick delta: ${delta}`)
35    }
36  }
37}
38</script>
39
40<style>
41#app {
42  position: absolute;
43  top: 0;
44  right: 0;
45  bottom: 0;
46  left: 0;
47}
48</style>

警告

  • 不幸的是,由于Vue,您需要有一个元素来渲染,如果您想将孩子添加到您的组件中,仍然存在任何容器组件的DOM表示
  • 您必须从应用程序中代理任何输入属性和输出事件。这与任何其他库不同,但如果您正在维持大图书馆的约束力
  • 虽然屏幕图形和渲染器是该技术的特别理想用例,但它可以应用到几乎任何东西,甚至是AJAX请求。

希望您对Vue组件可以做什么的知识已经显著扩大,想法像瀑布一样流动。

要小心了(不行!)

Published At
Categories with 技术
Tagged with
comments powered by Disqus