交互体验在项目开发是非常重要的东西,好的用户体验能为你的公司为你的产品带来意想不到的价值。若能开发出能让用户感受到我们的产品在和他友好互动。 相信用户的心情一定会很愉悦。那么我们有什么办法能开发出这样的产品呢? 今天就通过3个案例来给大家介绍下提升用户交互体验的一些技巧。
一、一个tab交互的优化过程
components 下新建ArticleTab 文件夹
ArticleTab下新建HotArticle 和LatestArticles两个组件
编写HotArticle 组件代码:
<template>
<div>
<div class="list">
<ul>
<li v-for="item in list" :key="item.id">
<a href="#">{{ item.title }}</a>
<span>{{ item.time }}</span>
</li>
</ul>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const list = ref([
{
id: 1,
title: 'DeepSeek建议:没存款的35岁该选什么工作?这3个选择比送外卖强'
},
{
id: 2,
title: '《哪吒2》:20句经典台词,句句封神,直戳人心!'
},
{
id: 3,
title: '中国保姆机器人火了!2025年正式量产,做饭保洁样样精通'
},
{
id: 4,
title: '单依纯北京演唱会造型,礼服设计的很独特呀'
},
{
id: 5,
title: '新能源汽车或将迎来有史以来最大的降价'
}
])
</script>
<style lang="scss" scoped>
.list {
height: 355px;
overflow-y: auto;
}
.list ul{
padding-left: 20px;
li {
padding: 10px 0;
display: flex;
justify-content: space-between;
a {
width: calc(100% - 80px);
text-decoration: none;
font-size: 18px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: #333;
}
}
}
</style>
编写LatestArticles 组件代码:
<template>
<div>
<div class="list">
<ul>
<li v-for="item in list" :key="item.id">
<a href="#">{{ item.title }}</a>
<span>{{ item.time }}</span>
</li>
</ul>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const list = ref([
{
id: 1,
title: '“数字杭州”科技引领活力奔涌'
},
{
id: 2,
title: '以更多实实在在的成果造福中非人民'
},
{
id: 3,
title: '“新”意满满 多地中小学迎接新学期'
},
{
id: 4,
title: '学习贯彻党的二十届三中全会精神'
},
{
id: 5,
title: '推动农民工向高素质产业工人转型'
}
])
</script>
<style lang="scss" scoped>
.list {
height: 355px;
overflow-y: auto;
}
.list ul{
padding-left: 20px;
li {
padding: 10px 0;
display: flex;
justify-content: space-between;
a {
width: calc(100% - 80px);
text-decoration: none;
font-size: 18px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: #333;
}
}
}
</style>
1. 生硬的tab 切换
<template>
<div class="wrap">
<div class="tab-bar">
<span
v-for="item in tabBars"
@click="setTab(item.name)"
:class="{ active: current === item.name}"
>{{ item.title }}</span>
</div>
<div class="tab-content">
<HotArticle v-if="current === 'HotArticle'" />
<LatestArticles v-else="current === 'LatestArticles'" />
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import HotArticle from '@/components/ArticleTab/HotArticle.vue';
import LatestArticles from '@/components/ArticleTab/LatestArticles.vue';
const tabBars = [
{ name: 'HotArticle', title: '热门文章'},
{ name: 'LatestArticles', title: '最新文章'},
]
const current = ref('HotArticle')
const setTab = (tab) => {
current.value = tab
}
</script>
<style lang="scss" scoped>
.wrap {
width: 600px;
height: 400px;
border: 1px solid #ddd;
}
.tab-bar {
display: flex;
justify-content: space-between;
span {
border-left: 1px solid #ddd;
border-bottom: 1px solid #ddd;
padding: 10px 0;
width: 50%;
text-align: center;
cursor: pointer;
&:nth-child(1) {
border-left: none;
}
&.active {
background: #f04142;
color: #fff;
}
}
}
</style>
这样一个普通的tab 切换功能就开发完成了, 我们来看下效果:
切换功能是实现了, 但是这非常生硬, 看着不是很自然。 有没有办法在切换的时候有个过程, 比如说慢慢的隐藏上一个,慢慢的显示下一个。我们可以借助Vue提供的一个内置组件Transition,我们不需要额外的导入,直接使用即可。
来看看具体使用方式:
2. 舒适的tab切换
<template>
<div class="wrap">
<div class="tab-bar">
<span
v-for="item in tabBars"
@click="setTab(item.name)"
:class="{ active: current === item.name}"
>{{ item.title }}</span>
</div>
<div class="tab-content">
<Transition>
<HotArticle v-if="current === 'HotArticle'" key="HotArticle" />
<LatestArticles v-else="current === 'LatestArticles'" key="LatestArticles" />
</Transition>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import HotArticle from '@/components/ArticleTab/HotArticle.vue';
import LatestArticles from '@/components/ArticleTab/LatestArticles.vue';
const tabBars = [
{ name: 'HotArticle', title: '热门文章'},
{ name: 'LatestArticles', title: '最新文章'},
]
const current = ref('HotArticle')
const setTab = (tab) => {
current.value = tab
}
</script>
<style lang="scss" scoped>
.wrap {
width: 600px;
height: 400px;
border: 1px solid #ddd;
}
.tab-bar {
display: flex;
justify-content: space-between;
span {
border-left: 1px solid #ddd;
border-bottom: 1px solid #ddd;
padding: 10px 0;
width: 50%;
text-align: center;
cursor: pointer;
&:nth-child(1) {
border-left: none;
}
&.active {
background: #f04142;
color: #fff;
}
}
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
.v-enter-active,
.v-leave-active{
transition: opacity 0.5s ease-in;
}
.v-enter-to,
.v-leave-from{
opacity: 1;
}
</style>
这个例子中我们主要做一下优化:
- 增加了
Transtion组件包裹tab 内容 style里面增加了一段样式 如下:
.v-enter-from,
.v-leave-to {
opacity: 0;
}
.v-enter-active,
.v-leave-active{
transition: opacity 0.5s ease-in;
}
.v-enter-to,
.v-leave-from{
opacity: 1;
}
- v-enter-from 进入的动画,刚进入的那一刻
- v-enter-active 进入动画从进入到进入完成的过程
- v-enter-to 进入动画完成的那一刻
- v-leave-from 离开动画刚离开的那一刻
- v-leave-active 离开动画从开始到结束的过程
- v-leave-to 离开结束的那一刻
现在我们来看下效果:
可以看到,过渡的效果是有了,但是有一个问题, 我们切换的时候tab 下方多了一个。 这是怎么回事呢? 元婴是我们切换的时候,进入的动画和离开的动画同时进行了。 我们怎么解决这个问题呢? 只需要修改下进入的动画。延迟0.5s 即可,即等离开的动画结束之后再开始进入的动画。修改如下:
将
.v-enter-active,
.v-leave-active{
transition: opacity 0.5s ease-in;
}
修改成:
.v-leave-active{
transition: opacity 0.5s ease-in;
}
.v-enter-active {
transition: opacity 0.5s ease-in 0.5s;
}
我们再来查看效果:
可以看到现在交效果就比较舒坦了。当然如果这个动画效果你的话,可以根据自己的想法调试出自己喜欢的一个效果。
这里效果我们是完成了, 但是我们现在的类名和现在的效果看起来有些怪异。我们现在做的是一个淡入淡出的效果。类名是v-... 这样的形式,有没有办法可以修改呢? 其实是可以的,我们只需要给Transition 组件上加上一个name="fade" 属性即可。
现在我们动画相关的样式即可以写成以下形式了:
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
.fade-leave-active{
transition: opacity 0.5s ease-in;
}
.fade-enter-active {
transition: opacity 0.5s ease-in 0.5s;
}
.fade-enter-to,
.fade-leave-from{
opacity: 1;
}
现在来看看效果是否正常:
可以看到效果依然好使。
Transition 除了能和v-if, v-else 一起使用外,还能和v-show, compenent动态组件一起使用。下面我们以动态组件为例来尝试下。
修改App.vue , 将
<HotArticle v-if="current === 'HotArticle'" key="HotArticle" />
<LatestArticles v-else="current === 'LatestArticles'" key="LatestArticles" />
改成:
<component :is="tabComs[current]"></component>
js 代码新增:
const tabComs = {
HotArticle,
LatestArticles
}
现在我们再来查看下效果:
二、表单提交的交互优化
这个例子中我们通过表单提交成功和提交失败的功能。 优化前后对比用户体验。
1.无情的表单交互
<template>
<div class="wrap">
<form @submit.prevent="submitForm">
<div class="form-item" >
<span>用户名:</span>
<input v-model="userName" />
</div>
<div class="form-item">
<span>密码:</span>
<input v-model="password" type="password" />
</div>
<div class="form-bottom">
<button type="submit">提交</button>
</div>
</form>
<div class="gray" v-if="showSuccess || showError"></div>
<div class="tip success-tip" v-if="showSuccess">
<img src="@/assets/images/tongue.svg"> 提交成功
</div>
<div class="tip error-tip" v-if="showError">
<img src="@/assets/images/face_worst.svg"> 提交失败
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const userName = ref('')
const password = ref('')
const showSuccess = ref(false)
const showError = ref(false)
const submitForm = () => {
if (userName.value && password.value) {
showSuccess.value = true
setTimeout(() => {
showSuccess.value = false
}, 3000)
} else {
showError.value = true
setTimeout(() => {
showError.value = false
}, 3000)
}
return false
}
</script>
<style lang="scss" scoped>
.form-item {
display: flex;
margin-top: 10px;
span {
width: 80px;
}
input {
border: none;
outline: none;
box-shadow: 0 0 0 1px #dcdfe6 inset;
border-radius: 4px;
padding: 1px 11px;
line-height: 28px;
}
}
.gray {
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
position: fixed;
left: 0;
top: 0;
}
.form-bottom {
margin-top: 20px;
button {
border: 1px solid #dcdfe6;
border-radius: 4px;
color: #606266;
padding: 8px 15px;
background: #fff;
cursor: pointer;
margin-left: 80px;
}
}
.tip {
position: fixed;
width: 180px;
height: 60px;
left: 50%;
top: 50%;
background: #fff;
border-radius: 4px;
transform: translate(-50%, -50%);
box-shadow: 0 0 3px 5px rgba(0, 0, 0, 0.3);
display: flex;
justify-content: center;
align-items: center;
color: #fff;
img {
width: 28px;
margin-right: 10px;
}
}
.success-tip {
background: #67c23a;
color: #fff;
}
.error-tip {
background: #f56c6c;
}
</style>
上面代码中:
- 我们只要有一个输入框没有输入东西,点击提交按钮时都会弹出提交失败的提示
- 当我们都输入了东西时, 点击提交按钮就会弹出提交成功的提示
来看看运行效果:
可以看到我们的交互效果也是非常生硬。没有感情。现在我们同样也为他添加一下动画效果。这里我们会用到一个第三方的动画库, animate.css
2. 具有互动性的表单交互开发过程
安装animate.css
pnpm i animate.css
修改之前的代码:
<template>
<div class="wrap">
<form @submit.prevent="submitForm">
<div class="form-item" >
<span>用户名:</span>
<input v-model="userName" />
</div>
<div class="form-item">
<span>密码:</span>
<input v-model="password" type="password" />
</div>
<div class="form-bottom">
<button type="submit">提交</button>
</div>
</form>
<div class="gray" v-if="showSuccess || showError"></div>
<Transition
name="custom-classes"
enter-active-class="animate__animated animate__shakeY"
leave-active-class="animate__animated animate__fadeOutUp"
>
<div class="tip success-tip" v-if="showSuccess">
<img src="@/assets/images/tongue.svg"> 提交成功
</div>
</Transition>
<Transition
enter-active-class="animate__animated animate__shakeX"
leave-active-class="animate__animated animate__fadeOutRightBig"
>
<div class="tip error-tip" v-if="showError">
<img src="@/assets/images/face_worst.svg"> 提交失败
</div>
</Transition>
</div>
</template>
<script setup>
import { ref } from 'vue'
import "animate.css";
const userName = ref('')
const password = ref('')
const showSuccess = ref(false)
const showError = ref(false)
const submitForm = () => {
if (userName.value && password.value) {
showSuccess.value = true
setTimeout(() => {
showSuccess.value = false
}, 3000)
} else {
showError.value = true
setTimeout(() => {
showError.value = false
}, 3000)
}
return false
}
</script>
<style lang="scss" scoped>
.form-item {
display: flex;
margin-top: 10px;
span {
width: 80px;
}
input {
border: none;
outline: none;
box-shadow: 0 0 0 1px #dcdfe6 inset;
border-radius: 4px;
padding: 1px 11px;
line-height: 28px;
}
}
.gray {
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
position: fixed;
left: 0;
top: 0;
}
.form-bottom {
margin-top: 20px;
button {
border: 1px solid #dcdfe6;
border-radius: 4px;
color: #606266;
padding: 8px 15px;
background: #fff;
cursor: pointer;
margin-left: 80px;
}
}
.tip {
position: fixed;
width: 180px;
height: 60px;
left: 50%;
top: 50%;
background: #fff;
border-radius: 4px;
transform: translate(-50%, -50%);
box-shadow: 0 0 3px 5px rgba(0, 0, 0, 0.3);
display: flex;
justify-content: center;
align-items: center;
color: #fff;
img {
width: 28px;
margin-right: 10px;
}
}
.success-tip {
background: #67c23a;
color: #fff;
}
.error-tip {
background: #f56c6c;
}
</style>
在上述代码中:
- 我们增加了
Transition组件包裹成功提示和失败的提示 - 在
Transition组件上我们增加了enter-active-class属性和leave-active-class属性。 这两个属性是用来自定义动画类名的,相当于之前的v-enter-active和v-leave-active - 我们导入了
animate.css
我们来看下运行效果:
可以看到现在效果看起来就舒服多了,成功了跳两下,就像真的在和你互动一样,成功了,开心得跳起来。失败了摇头表示不开心。
我们现在看到的都是单个的效果, 但是我们经常还会做一些列表的交互。下面我们来做一个列表相关的操作。
三、 生硬的表格交互效果
我们平常写的表格交互如下:
<template>
<table class="ui-table">
<thead>
<tr>
<td>日期</td>
<td>姓名</td>
<td>地址</td>
<td>操作</td>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in tableData" :key="index">
<td>{{ item.date }}</td>
<td>{{ item.name }}</td>
<td>{{ item.address }}</td>
<td>
<button class="del-button" @click="deleteItem(index)">删除</button>
</td>
</tr>
</tbody>
</table>
</template>
<script setup>
import { ref } from 'vue'
const tableData = ref([
{
date: '2016-05-03',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-02',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-04',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-01',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
])
const deleteItem = (index) => {
tableData.splice(index, 1)
}
</script>
<style lang="scss" scoped>
.ui-table{
border-collapse: collapse; /* 合并边框 */
border: 1px solid #dcdfe6; /* 设置表格外边框 */
width: 100%;
tr {
border-bottom: 1px solid #dcdfe6;
:deep(td){
padding: 10px;
}
}
.del-button {
border: 1px solid #f56c6c;
border-radius: 4px;
color: #606266;
padding: 8px 15px;
background: #f56c6c;
cursor: pointer;
color: #fff;
}
}
</style>
在上面代码中:
- 我们写了个table 列表
- 点击删除按钮可以进行删除 来查看下效果:
可以看到效果也是非常的生硬。
2. 让人感受到舒适的交互效果
现在我们来给他加下动画效果, 不过这次要使用新的组件TransitionGroup, 因为Transition 只能实现只有一个根标签的动画, 而我们这里是一个列表,所以需要使用TransitionGroup。 来看下具体使用方式:
<template>
<table class="ui-table">
<thead>
<tr>
<td>日期</td>
<td>姓名</td>
<td>地址</td>
<td>操作</td>
</tr>
</thead>
<TransitionGroup name="fade" tag="tbody">
<tr v-for="(item, index) in tableData" :key="index">
<td>{{ item.date }}</td>
<td>{{ item.name }}</td>
<td>{{ item.address }}</td>
<td>
<button class="del-button" @click="deleteItem(index)">删除</button>
</td>
</tr>
</TransitionGroup>
</table>
</template>
<script setup>
import { ref } from 'vue'
const tableData = ref([
{
date: '2016-05-03',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-02',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-04',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-01',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
])
const deleteItem = (index) => {
tableData.value.splice(index, 1)
}
</script>
<style lang="scss" scoped>
.ui-table{
border-collapse: collapse; /* 合并边框 */
border: 1px solid #dcdfe6; /* 设置表格外边框 */
width: 100%;
tr {
border-bottom: 1px solid #dcdfe6;
:deep(td){
padding: 10px;
}
}
.del-button {
border: 1px solid #f56c6c;
border-radius: 4px;
color: #606266;
padding: 8px 15px;
background: #f56c6c;
cursor: pointer;
color: #fff;
}
}
.fade-enter-from,
.fade-leave-to{
opacity: 0;
}
.fade-enter-active,
.fade-leave-active{
transition: all 0.5s;
}
.fade-enter-to,
.fade-leave-from {
opacity: 1;
}
</style>
在上面代码中:
- 我们用
TransitionGroup包裹了tbody中的tr TransitionGroup添加了name属性自定义类名的开头TransitionGroup添加了tag属性,用来指定渲染tr标签。TransitionGroup默认不渲染任何标签的,只有增加了tag属性才会渲染指定的标签。- 删除了
tbody中的tr, 因为他已经被TransitionGroup指定的tag属性代替 - 增加动画样式
现在来看下效果:
可以看到现在删除就会有一个过渡的效果,看着就比较舒服了。
总结
本篇通过3个案例介绍了几个常用优化用户体验的方法:
- 通过
Transition给一些单个根元素的结构提供一些过渡 - 还可以通过
Transition结合第三方动画库animate.css来提升用户的互动体验 - 通过
TransitionGroup可以提升一些列表交互的用户体验。
本文的介绍就介绍到这里了,感谢您的收看。