Vue2中如何优雅地使用外部工具函数

Vue2 中如何优雅地使用外部工具函数

前言

在 Vue2 项目开发中,我们经常需要引入一些通用的工具函数(如日期格式化、防抖节流等)。很多开发者会有疑问:为什么在 Vue 组件中不能直接使用 import 导入的函数?为什么在<template><button @click='func'></button></template>func不能直接绑定到 click 事件?

一、直接导入使用(推荐方式)

对于纯工具函数(不依赖 Vue 实例),最简单的使用方式就是直接导入:

1
2
3
4
// utils/date.js
export function formatDate(date) {
return date.toISOString().split("T")[0];
}
1
2
3
4
5
6
7
8
9
10
// MyComponent.vue
import { formatDate } from "@/utils/date";

export default {
methods: {
handleClick() {
console.log(formatDate(new Date())); // 直接调用
},
},
};

优点

  • 简单直接,符合 ES6 模块规范
  • 不会污染 Vue 实例
  • 适合不依赖 Vue 实例的纯函数

二、在模板中使用外部函数

由于 Vue 模板只能访问组件实例(this)上的属性和方法,我们需要通过以下方式将外部函数暴露给模板:

1. 通过 methods 包装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div>{{ formatDateInTemplate(currentDate) }}</div>
</template>

<script>
import { formatDate } from "@/utils/date";

export default {
data() {
return { currentDate: new Date() };
},
methods: {
formatDateInTemplate(date) {
return formatDate(date); // 包装后暴露给模板
},
},
};
</script>

2. 通过 computed 计算属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div>{{ formattedDate }}</div>
</template>

<script>
import { formatDate } from "@/utils/date";

export default {
data() {
return { currentDate: new Date() };
},
computed: {
formattedDate() {
return formatDate(this.currentDate);
},
},
};
</script>

三、全局方法挂载

对于高频使用的工具函数,可以挂载到 Vue 原型上:

1
2
3
4
5
// main.js
import Vue from "vue";
import { formatDate } from "@/utils/date";

Vue.prototype.$formatDate = formatDate;
1
2
3
4
5
6
7
8
9
10
11
<template>
<div>{{ $formatDate(currentDate) }}</div>
</template>

<script>
export default {
data() {
return { currentDate: new Date() };
},
};
</script>

适用场景

  • 多个组件都需要使用的工具函数
  • 需要保持一致的格式化逻辑

四、Vue2 过滤器(即将淘汰)

Vue2 提供了过滤器功能,但 Vue3 已废弃:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div>{{ currentDate | formatDate }}</div>
</template>

<script>
import { formatDate } from "@/utils/date";

export default {
data() {
return { currentDate: new Date() };
},
filters: {
formatDate(date) {
return formatDate(date);
},
},
};
</script>

五、为什么需要”包装”?

Vue 模板不能直接使用 import 的函数,主要基于以下设计考虑:

  1. 作用域隔离:模板应该只依赖组件内部状态
  2. 响应式追踪:Vue 需要知道哪些数据变化会影响渲染
  3. 代码可维护性:明确的依赖关系更利于团队协作

六、最佳实践建议

使用场景 推荐方案 示例
纯工具函数 直接导入 import { formatDate } from '@/utils'
模板中使用 methods/computed this.formatDate()
全局高频使用 挂载原型 Vue.prototype.$formatDate
Vue2 项目 过滤器 {{ date \| formatDate }}

结语

理解 Vue 的设计哲学,能帮助我们写出更优雅的代码。虽然需要多一层”包装”,但这正是 Vue 保持代码结构清晰的关键。根据不同的使用场景选择合适的方案,可以让我们的 Vue 项目更加可维护。

作者:你的名字
发布日期:2025 年 7 月 29 日
标签:Vue2, JavaScript, 前端开发

使用border-image实现图片边框效果详解

CSS的border-image属性允许开发者使用图片作为元素的边框,这为网页设计提供了更多创意可能。本文将详细解析border-image的各个参数,帮助你掌握这一强大的CSS特性。

border-image属性概述

border-image是一个复合属性,可以分解为以下子属性:

  • border-image-source:指定用作边框的图片
  • border-image-slice:定义如何切割图片
  • border-image-width:设置边框图片的宽度
  • border-image-outset:控制边框图片向外扩展
  • border-image-repeat:定义图片如何填充边框区域

参数详解

1. border-image-source

指定用作边框的图片路径,可以是URL或渐变:

1
2
3
.element {
border-image-source: url('border.png');
}

2. border-image-slice

定义如何切割图片,接受1-4个值(类似margin/padding的简写):

1
2
3
4
5
6
.element {
border-image-slice: 30; /* 所有边距30px */
border-image-slice: 30 20; /* 上下30px,左右20px */
border-image-slice: 30 20 10; /* 上30px,左右20px,下10px */
border-image-slice: 30 20 10 5; /* 上30px,右20px,下10px,左5px */
}

数值可以是像素值(不带单位)或百分比,表示从图片边缘向内切割的距离。

3. border-image-width

控制边框图片的显示宽度:

1
2
3
4
5
.element {
border-image-width: 20px; /* 所有边20px */
border-image-width: 20px 15px; /* 上下20px,左右15px */
border-image-width: auto; /* 使用border-width的值 */
}

4. border-image-outset

定义边框图片向外扩展的距离:

1
2
3
4
.element {
border-image-outset: 10px; /* 所有边扩展10px */
border-image-outset: 10px 5px; /* 上下10px,左右5px */
}

5. border-image-repeat

控制图片如何填充边框区域:

  • stretch:拉伸图片填充(默认)
  • repeat:平铺图片
  • round:类似repeat,但会调整图片尺寸使完整显示
  • space:类似repeat,但会添加空白使完整显示
1
2
3
4
5
6
.element {
border-image-repeat: stretch; /* 默认值 */
border-image-repeat: repeat;
border-image-repeat: round;
border-image-repeat: space;
}

复合写法示例

border-image的完整简写语法为:

1
border-image: source slice / width / outset repeat;

实际应用示例:

1
2
3
4
.fancy-border {
border: 20px solid transparent; /* 定义边框宽度和透明色 */
border-image: url('border.png') 30 / 20px / 5px round;
}

实用案例

1. 九宫格边框

1
2
3
4
5
6
.nine-grid {
width: 200px;
height: 200px;
border: 30px solid transparent;
border-image: url('nine-grid.png') 30 round;
}

2. 渐变边框

1
2
3
4
.gradient-border {
border: 10px solid;
border-image: linear-gradient(45deg, #f00, #00f) 1;
}

3. 复杂图案边框

1
2
3
4
.pattern-border {
border: 15px solid transparent;
border-image: url('floral-pattern.png') 50 / 15px / 0 stretch;
}

注意事项

  1. 兼容性:现代浏览器都支持border-image,但IE10及以下版本支持不完整
  2. 备用方案:始终设置常规border作为备用
  3. 图片选择:选择适合平铺或拉伸的边框图片
  4. 性能考虑:复杂的边框图片可能影响渲染性能
  5. 移动端:在高DPI设备上可能需要提供@2x图片

总结

border-image属性为CSS边框带来了无限可能,通过合理设置slice、width和repeat参数,可以创建出各种独特的边框效果。

解决伪元素三角形box-shadow阴影不合理的问题

在CSS中,我们经常使用伪元素(::before, ::after)来创建各种形状,特别是三角形。然而,当我们需要为这些三角形添加box-shadow时,经常会遇到阴影显示不合理的问题。本文将探讨这个问题的原因及解决方案。

问题描述

当我们使用边框法创建三角形并尝试添加box-shadow时,阴影会出现在包含三角形的矩形框上,而不是三角形本身:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.triangle {
position: relative;
width: 100px;
height: 100px;
}

.triangle::after {
content: '';
position: absolute;
width: 0;
height: 0;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-bottom: 100px solid #3498db;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
}

上面的代码会在整个伪元素的矩形区域上显示阴影,而不是只在下方的三角形上。

解决方案

方法1:使用filter: drop-shadow

filter: drop-shadow 可以正确识别元素的透明部分,只对有颜色的部分应用阴影:

1
2
3
4
.triangle::after {
/* 三角形代码同上 */
filter: drop-shadow(0 0 10px rgba(0, 0, 0, 0.5));
}

方法2:使用clip-path

结合clip-path裁剪出三角形形状,然后应用box-shadow:

1
2
3
4
5
6
7
8
9
.triangle::after {
content: '';
position: absolute;
width: 100px;
height: 100px;
background: #3498db;
clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
}

方法3:使用SVG

使用SVG创建三角形可以更精确地控制阴影:

1
2
3
4
5
6
<div class="triangle">
<svg width="100" height="100" viewBox="0 0 100 100">
<polygon points="50,0 0,100 100,100" fill="#3498db"
filter="drop-shadow(0 0 10px rgba(0,0,0,0.5))"/>
</svg>
</div>

注意事项

  1. filter: drop-shadow 的性能消耗比 box-shadow 略高,在动画中大量使用可能导致性能问题
  2. clip-path的浏览器兼容性较好,但某些旧版本浏览器可能需要前缀
  3. SVG方案兼容性最好,但会增加DOM节点
  4. 如果必须使用边框法创建三角形,可以考虑在外部容器上应用阴影

总结

对于伪元素创建的三角形阴影问题,推荐优先使用filter: drop-shadow方案,它既能保持代码简洁,又能正确显示阴影。在需要更复杂形状或更好性能时,可以考虑clip-path或SVG方案。

解决HTML img元素底部幽灵空隙问题

在使用div包裹<img>元素时,可能会遇到元素底部出现异常空隙的问题。

问题原因

  1. 行内元素的基线对齐特性
  2. 父容器的默认行高设置
  3. 图片与文本混合时的垂直对齐问题

解决方案

方法一:设置图片为块级元素

1
<img src="/path/to/image.jpg" style="display: block;" />

或在 CSS 中全局设置:

1
2
3
img {
display: block;
}

方法二:调整垂直对齐方式

1
2
3
<img
src="/path/to/image.jpg"
style="vertical-align: middle; display: inline-block;" />

方法三:移除父元素的行高

1
2
3
.post-content img {
line-height: 0;
}

方法四:使用负边距(不推荐)

1
2
3
img {
margin-bottom: -4px;
}

商品图片放大镜效果

注意的点

  1. 取景框要设置csspointer-events: none;,否则鼠标移动到取景框上时,会触发鼠标移动事件,导致位置抽搐。
  2. 图片幽灵缝隙问题,可以考虑display: flex;或者其他的解决办法。
  3. 使用transform代替topleft,避免回流,优化性能。
  4. 预览图的定位问题。
  5. 考虑取景框边框对放大预览图片的影响。

直接贴代码:

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
<template>
<div class="container flex">
<div class="img" @mousemove="onMousemove">
<img src="../../public/logos/avatar1.png" alt="" ref="img">
<div ref="small" class="small"></div>
</div>
<div class="prev">
<img src="../../public/logos/avatar1.png" alt="" ref="prevImg">
</div>
</div>
</template>

<script setup>
import { onMounted, ref } from 'vue';

const scale = ref(1);
const img = ref(null);
const small = ref(null);
const prevImg = ref(null);
const offsetX = ref(0);
const offsetY = ref(0);

function checkElements() {
if (!img.value) throw new Error('找不到图片...');
if (!small.value) throw new Error('找不到取景框...');
if (!prevImg.value) throw new Error('找不到预览图...');
}

onMounted(() => {
checkElements();
const imgWidth = img.value.offsetWidth;
const smallWidth = small.value.offsetWidth;

scale.value = imgWidth / smallWidth;
prevImg.value.style.transform = `scale(${scale.value})`;
})

function onMousemove(e) {
checkElements();
offsetX.value = Math.max(0, e.offsetX);
offsetY.value = Math.max(0, e.offsetY);

const imgWidth = img.value.offsetWidth;
const imgHeight = img.value.offsetHeight;
const smallWidth = small.value.offsetWidth;
const smallHeight = small.value.offsetHeight;

updatePrev({ offsetX: offsetX.value, offsetY: offsetY.value, imgWidth, imgHeight, smallWidth, smallHeight });
}

function updatePrev({ offsetX, offsetY, imgWidth, imgHeight, smallWidth, smallHeight }) {
const position = {
x: Math.max(0, Math.min(offsetX - smallWidth / 2, imgWidth - smallWidth)),
y: Math.max(0, Math.min(offsetY - smallHeight / 2, imgHeight - smallHeight)),
}

small.value.style.transform = `translate(${position.x}px, ${position.y}px)`;
prevImg.value.style.transform = `translate(${-(position.x - smallWidth) * scale.value}px, ${-(position.y - smallHeight) * scale.value}px) scale(${scale.value})`;
}
</script>

<style lang='less' scoped>
.container {
width: 100%;
height: 700px;
display: flex;
justify-content: center;
border: 1px rgb(0, 0, 0) solid;
flex-direction: column;
align-items: center;
gap: 10px;
}

img {
width: 100%;
vertical-align: bottom;
}

.img {
width: 300px;
height: 300px;
border: 2px solid gray;
position: relative;
cursor: move;
display: flex;


.small {
width: 100px;
height: 100px;
border: 1.5px solid rgba(127, 255, 212, 1);
position: absolute;
top: 0;
left: 0;
background-color: rgba(127, 255, 212, 0.382);
z-index: 10;
pointer-events: none; // 防止鼠标移动到取景框上时,触发鼠标移动事件,导致位置抽搐。
box-sizing: border-box;
}
}

.prev {
width: 300px;
height: 300px;
border: 1.5px dashed gray;
overflow: hidden;
position: relative;
display: flex;

img {
position: absolute;
top: 0;
right: 0;
}
}
</style>