本文是可视化拖拽系列的第四篇,比起之前的三篇文章,这篇功能点要稍微少一点,总共有五点:
- SVG 组件
- 动态属性面板
- 数据来源(接口请求)
- 组件联动
- 组件按需加载
如果你对我之前的系列文章不是很了解,建议先把这三篇文章看一遍,再来阅读本文(否则没有上下文,不太好理解):
另附上项目、在线 DEMO 地址:
SVG 组件
目前项目里提供的自定义组件都是支持自由放大缩小的,不过他们有一个共同点——都是规则形状。也就是说对它们放大缩小,直接改变宽高就可以实现了,无需做其他处理。但是不规则形状就不一样了,譬如一个五角星,你得考虑放大缩小时,如何成比例的改变尺寸。最终,我采用了 svg 的方案来实现(还考虑过用 iconfont 来实现,不过有缺陷,放弃了),下面让我们来看看具体的实现细节。
用 SVG 画一个五角星
假设我们需要画一个 100 * 100 的五角星,它的代码是这样的:
<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg" > <polygon points="50 0,62.5 37.5,100 37.5,75 62.5,87.5 100,50 75,12.5 100,25 62.5,0 37.5,37.5 37.5" stroke="#000" fill="rgba(255, 255, 255, 1)" stroke-width="1" ></polygon> </svg>
svg 上的版本、命名空间之类的属性不是很重要,可以先忽略。重点是 polygon 这个元素,它在 svg 中定义了一个由一组首尾相连的直线线段构成的闭合多边形形状
,最后一点连接到第一点。也就是说这个多边形由一系列坐标点组成,相连的点之间会自动连上。polygon 的 points 属性用来表示多边形的一系列坐标点,每个坐标点由 x y 坐标组成,每个坐标点之间用 ,
逗号分隔。
上图就是一个用 svg 画的五角星,它由十个坐标点组成 50 0,62.5 37.5,100 37.5,75 62.5,87.5 100,50 75,12.5 100,25 62.5,0 37.5,37.5 37.5
。由于这是一个 100*100 的五角星,所以我们能够很容易的根据每个坐标点的数值算出它们在五角星(坐标系)中所占的比例。譬如第一个点是 p1(50,0
),那么它的 x y 坐标比例是 50%, 0
;第二个点 p2(62.5,37.5
),对应的比例是 62.5%, 37.5%
...
// 五角星十个坐标点的比例集合 const points = [ [0.5, 0], [0.625, 0.375], [1, 0.375], [0.75, 0.625], [0.875, 1], [0.5, 0.75], [0.125, 1], [0.25, 0.625], [0, 0.375], [0.375, 0.375], ]
既然知道了五角星的比例,那么要画出其他尺寸的五角星也就易如反掌了。我们只需要在每次对五角星进行放大缩小,改变它的尺寸时,等比例的给出每个坐标点的具体数值即要。
<div class="svg-star-container"> <svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg" > <polygon ref="star" :points="points" :stroke="element.style.borderColor" :fill="element.style.backgroundColor" stroke-width="1" /> </svg> <v-text :prop-value="element.propValue" :element="element" /> </div> <script> function drawPolygon(width, height) { // 五角星十个坐标点的比例集合 const points = [ [0.5, 0], [0.625, 0.375], [1, 0.375], [0.75, 0.625], [0.875, 1], [0.5, 0.75], [0.125, 1], [0.25, 0.625], [0, 0.375], [0.375, 0.375], ] const coordinatePoints = points.map(point => width * point[0] + ' ' + height * point[1]) this.points = coordinatePoints.toString() // 得出五角星的 points 属性数据 } </script>
其他 SVG 组件
同理,要画其他类型的 svg 组件,我们只要知道它们坐标点所占的比例就可以了。如果你不知道一个 svg 怎么画,可以网上搜一下,先找一个能用的 svg 代码(这个五角星的 svg 代码,就是在网上找的)。然后再计算它们每个坐标点所占的比例,转成小数点的形式,最后把这些数据代入上面提供的 drawPolygon()
函数即可。譬如画一个三角形的代码是这样的:
function drawTriangle(width, height) { const points = [ [0.5, 0.05], [1, 0.95], [0, 0.95], ] const coordinatePoints = points.map(point => width * point[0] + ' ' + height * point[1]) this.points = coordinatePoints.toString() // 得出三角形的 points 属性数据 }
动态属性面板
目前所有自定义组件的属性面板都共用同一个 AttrList 组件。因此弊端很明显,需要在这里写很多 if 语句,因为不同的组件有不同的属性。例如矩形组件有 content 属性,但是图片没有,一个不同的属性就得写一个 if 语句。
<el-form-item v-if="name === 'rectShape'" label="内容"> <el-input /> </el-form-item> <!-- 其他属性... -->
幸好,这个问题的解决方案也不难。在本系列的第一篇文章中,有讲解过如何动态渲染自定义组件:
<component :is="item.component"></component> <!-- 动态渲染组件 -->
在每个自定义组件的数据结构中都有一个 component
属性,这是该组件在 Vue 中注册的名称。因此,每个自定义组件的属性面板可以和组件本身一样(利用 component
属性),做成动态的:
<!-- 右侧属性列表 --> <section class="right"> <el-tabs v-if="curComponent" v-model="activeName"> <el-tab-pane label="属性" name="attr"> <component :is="curComponent.component + 'Attr'" /> <!-- 动态渲染属性面板 --> </el-tab-pane> <el-tab-pane label="动画" name="animation" style="padding-top: 20px;"> <AnimationList /> </el-tab-pane> <el-tab-pane label="事件" name="events" style="padding-top: 20px;"> <EventList /> </el-tab-pane> </el-tabs> <CanvasAttr v-else></CanvasAttr> </section>
同时,自定义组件的目录结构也需要做下调整,原来的目录结构为:
- VText.vue - Picture.vue ...
调整后变为:
- VText - Attr.vue <!-- 组件的属性面板 --> - Component.vue <!-- 组件本身 --> - Picture - Attr.vue - Component.vue
现在每一个组件都包含了组件本身和它的属性面板。经过改造后,图片属性面板代码也更加精简了:
<template> <div class="attr-list"> <CommonAttr></CommonAttr> <!-- 通用属性 --> <el-form> <el-form-item label="镜像翻转"> <div style="clear: both;"> <el-checkbox v-model="curComponent.propValue.flip.horizontal" label="horizontal">水平翻转</el-checkbox> <el-checkbox v-model="curComponent.propValue.flip.vertical" label="vertical">垂直翻转</el-checkbox> </div> </el-form-item> </el-form> </div> </template>
这样一来,组件和对应的属性面板都变成动态的了。以后需要单独给某个自定义组件添加属性就非常方便了。