前言
这是一篇新手面试级别的题目解析,欢迎讨论
昨天在群里看到有人发了一道轮播图的面试题,用来面试新人,感觉还挺不错的,非常适合考察新手对 Vue3 和 JavaScript 的理解程度,于是我便自己写了一份 demo 分享出来。
题目
题目如下所示:
代码
初始化
首先我们可以直接用vite
构建项目,并且为了方便我还安装了tailwindcss
作为辅助:
$ npm create vite@latest my-vue-app -- --template vue
$ npm install -D tailwindcss postcss autoprefixernpx && tailwindcss init -p
然后更新tailwind.config.js
文件,注意 content 部分,将你需要用到 tailwind 的地方包含进来:
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
然后,记得更新style.css
内容:
@tailwind base;
@tailwind components;
@tailwind utilities;
并且注意在main.js
处引入style.css
。
Template 模版
模版代码如下:
<template>
<div @mouseenter="onMouseEnter" @mouseleave="() => autoPlay()" class="container group select-none" :style="{
'--left': left + 'px'
}">
<div class="wrap">
<img v-for="url in imgList" :src="url" :key="url" alt="" />
</div>
<ul
class="absolute z-10 inset-x-0 -bottom-12 group-hover:bottom-0 transition-all flex items-center justify-center gap-4 p-4">
<li :class="['w-3 h-3 rounded-full cursor-pointer', left === index * -800 ? 'bg-blue-500' : 'bg-sky-50/40']"
v-for="(_, index) in 3" :key="index" @click="handleClick(index)">
</li>
</ul>
<div
class="absolute px-2 -left-12 inset-y-0 flex items-center transition-all delay-100 ease-in-out group-hover:left-0 bg-transparent group-hover:bg-gray-400/10"
:style="{
opacity: left === 0 ? 0.2 : 1
}">
<span class="cursor-pointer bg-blue-50 rounded-full p-1" @click="() => {
if (left === 0) return
left += 800
}">
< </span>
</div>
<div
class="absolute px-2 -right-12 inset-y-0 flex items-center transition-all delay-100 ease-in-out group-hover:right-0 bg-transparent group-hover:bg-gray-400/10"
:style="{
opacity: left === -(imgList.length - 1) * 800 ? 0.2 : 1
}">
<span class="cursor-pointer bg-blue-50 rounded-full p-1" @click="() => {
if (left === -(imgList.length - 1) * 800) return
left -= 800
}">
>
</span>
</div>
</div>
</template>
组件 setup
组件 setup 部分代码如下:
<script setup>
import { onMounted, onUnmounted, ref } from 'vue';
const left = ref(0)
const imgList = [
'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?q=80&w=2673&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
'https://images.unsplash.com/photo-1439405326854-014607f694d7?q=80&w=2670&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
'https://images.unsplash.com/photo-1471922694854-ff1b63b20054?q=80&w=2672&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D'
]
// 处理点击事件
const handleClick = (index) => {
// 计算起始位置
left.value = -800 * index
}
// 暂停
const onMouseEnter = () => {
clearInterval(timer)
}
// 定时器
let timer = null
// 根据 flag 判断向前还是向后播放
let positive = true
const autoPlay = () => {
timer = setInterval(() => {
if (left.value === -(imgList.length - 1) * 800) {
positive = false
} else if (left.value === 0) {
positive = true
}
if (positive) {
left.value -= 800
} else {
left.value += 800
}
}, 2000)
}
// 初始化
onMounted(autoPlay)
// 销毁
onUnmounted(() => {
clearInterval(timer)
})
</script>
样式
<style scoped>
/* 容器大小 */
.container {
height: 300px;
width: 800px;
overflow: hidden;
position: relative;
margin: 100px auto;
}
/* .wrap */
.wrap {
position: relative;
left: var(--left);
width: 2400px;
transition: left 0.6s ease-in-out;
}
/* 图片大小 */
.wrap img {
width: 800px;
float: left;
height: 300px;
display: block;
}
</style>
解析
轮播图我们可以有很多种实现方式,本文介绍的思路和代码段,某种意义上是为了引出一些技术点,如果你不知道这些知识是什么,然后自己去查询学习,那么这将是一件好事。
首先,我们定义了三张图片地址构成的imgList
数组和left
值,我们给轮播图添加了类名为container
的容器,三张图放在.wrap
下,通过核心的overflow
属性去控制相对位置,来让主要显示区域显示不同的图片,达到轮播的效果。
left
值用来创建一个--left
变量(css 变量),用于相对位置设定。
你可以在.wrap
样式下看到这个用法,并且我们通过transition
来添加动画效果,指定属性和时间、动画渐入渐出的效果。
这时候,我们首先要实现自动轮播的效果,那么我们只要设置一个函数autoPlay
,定时地修改left
相对位置即可。
这个函数的核心在于:
- 设置定时器,用于在需要的时候控制清除定时器
- 判断当前是向前还是向后轮播(左右皆可),当播放到最后一张图的时候我们可以向前,效果看起来好一些
- 调整相对位置的像素值,这里仅需要注意图片个数和宽度即可
我们利用Vue
的生命周期函数,在mounted
之后执行autoPlay
,在unMounted
之后清除定时器。与此同时,利用mouseenter
和mouseleave
事件去实现清理定时器和重新运行轮播即可。
更多建议
对于这个Vue
项目来说,以下几点是笔者扩展方向的建议:
- 使用
TypeScript
进行开发 - 使用
UnoCss
配合Icons preset
来写样式和添加 svg 图标 - 将轮播图封装成组件,通过一个基础组件内部添加插槽和传给基础组件初始化配置,开启和关闭不同的功能
- 通过
provider
和inject
即可在父子组件之间传递数据,通过pinia
管理全局共用数据和持久化 - 通过
ky
封装请求,通过TanStack Query
去管理API
数据和缓存
如果你对这些内容不太了解,那么非常推荐你进一步去接触这些东西。
好了,下次见。
其他
ps: 欢迎讨论