程序员玛丽的星海方舟

Vue组件复制实现模块放大终极优化版

继上章研究出模块放大的实现方法并选择了 Vue 组件复制的方法后,将该方法投入了实践。在开发过程中还是觉得之前设计的调用方式有些繁琐,于是灵机一动,改良了一下代码。接下来将放出完整的关键代码,没看上一章也没关系。

用于渲染的组件

enlarge-wrapper.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<template>
<div v-show="component" id="EnlargeWrapperArea">
<!-- 遮罩,点击关闭 -->
<div id="EnlargeWrapperAreaMask" @click="close"></div>
<!-- 中心的一层包装,可设置背景 -->
<div id="EnlargeWrapper" :style="{ width: finalWidth + 'px', height: finalHeight + 'px' }">
<!-- 这里设置了一个enlarged属性来便于组件内部分辨是否被放大,按需添加 -->
<component
:is="component"
v-bind="props"
style="transform-origin: top left"
:style="{
width: initialWidth + 'px',
height: initialHeight + 'px',
transform: `scale(${scale})`,
}"
enlarged
></component>
</div>
</div>
</template>
<script>
export default {
data() {
return {
component: null, // 渲染放大的组件,顺便控制是否显示
props: {}, // 将输入组件的参数
initialWidth: 0, // 原始组件的宽度
initialHeight: 0, // 原始组件的高度
finalWidth: 0, // 放大后的宽度
finalHeight: 0, // 放大后的高度
scale: 1, // 应放大的倍数
};
},
methods: {
// 接收参数并显示浮层的方法
handler(component, initialWidth, initialHeight, props = {}) {
if (!component || !initialWidth || !initialHeight) {
// 预防异常
return;
}
this.initialWidth = initialWidth;
this.initialHeight = initialHeight;
// 根据情况计算应放大的效果,获取finalWidth, finalHeight和scale.
const maxWidth = 2400, maxHeight = 1200;
const maxWidthScale = maxWidth / this.initialWidth, maxHeightScale = maxHeight / this.initialHeight;
this.scale = Math.min(maxWidthScale, maxHeightScale);
this.finalWidth = this.initialWidth * this.scale;
this.finalHeight = this.initialHeight * this.scale;
// 设置组件和参数,将会开始渲染
this.component = component;
this.props = props;
},
// 关闭浮层的方法
close() {
this.component = null;
this.props = {};
}
}
}
</script>
<style scoped>
#EnlargeWrapperArea {
/* 覆盖全屏 */
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 9999;
}
#EnlargeWrapperAreaMask {
/* 覆盖全屏,第一层 */
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(0, 0, 0, 0.5); /* 遮罩颜色 */
}
#EnlargeWrapper {
/* 居中,第二层 */
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: #061d33; /* 设置的深色背景颜色,用于显示浅色调的内容 */
margin: auto;
}
</style>

该组件需要被设置在较低层级的地方,如#app 元素中,以使其 z-index 能否覆盖所有的图层。

App.vue
1
2
3
4
<div id="app">
<router-view></router-view>
<enlarge-wrapper></enlarge-wrapper>
</div>

调用方式

上一个版本需要更改父组件,较为繁琐,本版本仅需操作需要复制放大的组件。

如果父组件中注册了我们的目标组件,名称为TargetComponent:

1
2
3
4
5
6
7
8
9
<template>
<target-component></target-component>
</template>
<script>
import TargetComponent from '[path]';
export default {
components: { TargetComponent }
}
</script>

在子组件中,需要这样调用放大功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<template>
<!-- 需要知道自己在父组件中的注册名 -->
<button @click="enlarge('TargetComponent')">Enlarge Myself</button>
</template>
<script>
export default {
/* 按需获取enlarged属性 */
/*
props: {
enlarged: {
type: Boolean,
default: false
}
},
*/
methods: {
enlarge(name) {
const el = this.$el;
const component = this.$parent?.$options?.components[name];
if (!el || !component) {
return;
}
// 保存当前的props列表及对应的值,用于再次渲染
// 过滤enlarged属性,因为在enlarge-wrapper中会直接置为true
// 保存成键值对的形式,用于传递给v-bind方法
const propsNameList = Object.keys(this.$options.props).filter(v => v !== 'enlarged');
const props = propsNameList.reduce((cum, v) => {
cum[v] = this[v];
return cum;
}, {});
// 触发enlarge-wrapper组件中的handler方法,这里用的事件总线
this.$bus.emit('enlarge', component, el.clientWidth, el.clientHeight, props);
}
}
}
</script>

当然,你可以将以上的props和methods保存到mixin,方便在每个需要放大的组件中调用。

总结

虽然优化的代码行数不多,但是可以把代码局限在需要调用的目标组件中,缩小影响的代码范围。此外,能够复制props也是比较有用的功能拓展。