虽然在大多数 Web 应用程序中,您可能会将其渲染到 DOM,但有一些特殊情况下,您可能希望使用 Vue 用于其他用途。 假设您正在使用 WebGL 开发应用程序,您希望将其描述为 Vue 作为组件树。
我们将使用 pixi.js为本指南,所以请确保通过npm安装它. 这是一个先进的文章,因此,假定你已经有一个骨骼应用程序准备与webpack和View 2.2+. 此外,解释将主要通过代码评论由于组件的复杂性发生。
目标将是制作一组三个Vue组件,渲染器,容器和一个sprite组件,以便使用pixi.js在2D WebGL画布中绘制纹理。
最终的结果应该看起来像这样:
注意:这不是在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组件可以做什么的知识已经显著扩大,想法像瀑布一样流动。
要小心了(不行!)