随便聊两句
哈喽,大家好😁!我是敲里个敲敲
,前端领域摸爬滚打者,希望能通过这篇文章一起学习,一起收获。
快下班时候,老板突然叫我过去:
老板:我有一个idea,你这个搜索框可以这样效果,是不是更好,更符合用户使用?辛苦一下搞出来,看好你!
敲里个敲敲:老板,我也有idea,涨薪能激发员工动力,写的更快更好!
真实回答:
卑微小敲:好的领导!
拿钱办事,哎,加班!
UI设计及需求
UI设计如上,不单单需要鼠标滚轮上下滑动,而且需要键盘按钮⬆️⬇️实现上下翻动效果。
技术栈:vue3+ts+Ant design vue
下面我将一步步构建出来,Let's Go!
第一步:实现聚焦与失焦
xml
<template>
<div class="container">
<a-input
id="input"
class="search-input"
v-model:value="searchValue"
type="text"
placeholder="Search"
allow-clear
>
<template #prefix> <MonitorOutlined /> </template>
</a-input>
</div>
</template>
<script setup lang="ts">
import { MonitorOutlined } from '@ant-design/icons-vue'
import { ref } from 'vue'
const searchValue = ref('')
</script>
<style lang="less" scoped>
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.search-input {
position: absolute;
left: 0;
width: 230px;
}
:deep(.ant-input-affix-wrapper) {
&-focused {
width: 268px;
}
}
</style>
这里就是简单的引入,根据聚焦时候改变输入框宽度,没什么好说的😎😎😎
第二步:聚焦状态搜索展示结果
聚焦展示两种结果:
1、无数据,展示当前没有数据
2、存在数据,展示数据
xml
<template>
<div class="container">
<a-input
id="input"
class="search-input"
v-model:value="searchValue"
type="text"
placeholder="Search"
allow-clear
@change="onSearch"
>
<template #prefix> <MonitorOutlined /> </template>
</a-input>
<div class="search-result">
<!-- 有数据 -->
<div v-if="searchResult?.length">
<div
class="search-result-item"
v-for="(item, index) in searchResult"
:key="index"
@click="itemSelected(item)"
>
<span v-if="item">{{ item }}</span>
</div>
</div>
<!-- 无数据 -->
<div class="no-result" v-else>
<span>No search results</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { MonitorOutlined } from '@ant-design/icons-vue'
import { ref } from 'vue'
// 搜索框输入数据
const searchValue = ref('')
//-----------------新增:搜索结果
const searchResult = ref()
//-----------------新增:模拟数据库数据数据
const data = [
'richard',
'tom',
'lucy',
'jerry',
'jxxx',
'yyyj',
'ajax',
'exngj',
'jerry',
'jxxx',
'yyyj',
'ajax',
'exngj',
'lily',
'lucy',
'michael',
'张三',
'李四',
'王五',
'赵六',
]
//-----------------新增: 获取搜索结果
const onSearch = () => {
// 无输入则不搜索
if (searchValue.value === '') {
return (searchResult.value = [])
}
// 搜索
searchResult.value = data.filter((item) => item.includes(searchValue.value))
}
//-----------------新增: 选中的item
const itemSelected = (item: string) => {
console.log(item)
}
</script>
<style lang="less" scoped>
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: absolute;
left: 0;
color: black;
}
.search-input {
width: 230px;
}
.search-result {
display: flex;
width: 268px;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100px;
max-height: 200px;
overflow-y: scroll;
background: #fff;
border-radius: 12px;
.no-result {
width: 100%;
cursor: pointer;
text-align: center;
> span {
display: block;
padding: 12px 0;
}
}
.search-result-item {
width: 100%;
cursor: pointer;
text-align: center;
> span {
display: block;
padding: 12px 0;
}
}
.search-result-item:hover {
background: #ccc;
}
}
:deep(.ant-input-affix-wrapper) {
&-focused {
width: 268px;
}
}
::-webkit-scrollbar {
display: none;
}
</style>
解释:
- 将模拟数据作为数据库数据,然后根据用户输入去过滤符合的数据(防抖节流等优化各位自己加吧,敲子我就偷个懒了🤣🤣🤣)
- 根据匹配数据,展示结果
第三步:搜索结果动态展现与消失
聪明的你一定发现了下面这个问题:
对!结果框一直存在并且不关闭,接下来我们关闭一下他 HTML中增加: @blur="blurFn" @focus="focusFn"
来控制聚焦失焦,我们只要做到聚焦展示失焦消失即可。 完整代码如下:
xml
<template>
<div class="container">
<a-input
id="input"
class="search-input"
v-model:value="searchValue"
type="text"
placeholder="Search"
allow-clear
@change="onSearch"
@blur="blurFn"
@focus="focusFn"
>
<template #prefix> <MonitorOutlined /> </template>
</a-input>
<div class="search-result" v-if="isFocus">
<!-- 有数据 -->
<div v-if="searchResult?.length">
<div
class="search-result-item"
v-for="(item, index) in searchResult"
:key="index"
@click="itemSelected(item)"
>
<span v-if="item">{{ item }}</span>
</div>
</div>
<!-- 无数据 -->
<div class="no-result" v-else>
<span>No search results</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { MonitorOutlined } from '@ant-design/icons-vue'
import { ref } from 'vue'
// 搜索框输入数据
const searchValue = ref('')
// 搜索结果
const searchResult = ref()
//-----------------新增: 输入框是否获取焦点
const isFocus = ref(false)
// 模拟数据库数据数据
const data = [
'richard',
'tom',
'lucy',
'jerry',
'jxxx',
'yyyj',
'ajax',
'exngj',
'jerry',
'jxxx',
'yyyj',
'ajax',
'exngj',
'lily',
'lucy',
'michael',
'张三',
'李四',
'王五',
'赵六',
]
// 获取搜索结果
const onSearch = () => {
// 无输入则不搜索
if (searchValue.value === '') {
return (searchResult.value = [])
}
// 搜索
searchResult.value = data.filter((item) => item.includes(searchValue.value))
}
// 选中的item
const itemSelected = (item: string) => {
console.log(item)
}
//-----------------新增: 输入框获取焦点
function focusFn() {
isFocus.value = true
}
//-----------------新增:输入框失焦
function blurFn() {
isFocus.value = false
}
</script>
<style lang="less" scoped>
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: absolute;
left: 0;
color: black;
}
.search-input {
width: 230px;
}
.search-result {
display: flex;
width: 268px;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100px;
max-height: 200px;
overflow-y: scroll;
background: #fff;
border-radius: 12px;
.no-result {
width: 100%;
cursor: pointer;
text-align: center;
> span {
display: block;
padding: 12px 0;
}
}
.search-result-item {
width: 100%;
cursor: pointer;
text-align: center;
> span {
display: block;
padding: 12px 0;
}
}
.search-result-item:hover {
background: #ccc;
}
}
:deep(.ant-input-affix-wrapper) {
&-focused {
width: 268px;
}
}
::-webkit-scrollbar {
display: none;
}
</style>
第三步:实现匹配数据变色
当我们搜索的内容和搜索结果一样时候,匹配到的字段变蓝色,如下:
其实就是html中使用v-html渲染 <span v-html="highlightedText(item)"></span>
然后就是实现highlightedText函数:
javascript
function highlightedText(title: string) {
if (!searchValue.value) {
return title
}
// 创建正则表达式,用于查找并高亮显示匹配的部分
const regex = new RegExp(`(${searchValue.value})`, 'gi')
// 使用 replace 将匹配部分包裹在 <span> 中,并设置颜色
return title.replace(regex, '<span style="color: #1800D3;">$1</span>')
}
完整代码如下:
xml
<template>
<div class="container">
<a-input
id="input"
class="search-input"
v-model:value="searchValue"
type="text"
placeholder="Search"
allow-clear
@change="onSearch"
@blur="blurFn"
@focus="focusFn"
>
<template #prefix> <MonitorOutlined /> </template>
</a-input>
<div class="search-result" v-if="isFocus">
<!-- 有数据 -->
<div v-if="searchResult?.length">
<div
class="search-result-item"
v-for="(item, index) in searchResult"
:key="index"
@click="itemSelected(item)"
>
<span v-html="highlightedText(item)"></span>
</div>
</div>
<!-- 无数据 -->
<div class="no-result" v-else>
<span>No search results</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { MonitorOutlined } from '@ant-design/icons-vue'
import { ref } from 'vue'
// 搜索框输入数据
const searchValue = ref('')
// 搜索结果
const searchResult = ref()
// 输入框是否获取焦点
const isFocus = ref(false)
// 模拟数据库数据数据
const data = [
'richard',
'tom',
'lucy',
'jerry',
'jxxx',
'yyyj',
'ajax',
'exngj',
'jerry',
'jxxx',
'yyyj',
'ajax',
'exngj',
'lily',
'lucy',
'michael',
'张三',
'李四',
'王五',
'赵六',
]
// 获取搜索结果
const onSearch = () => {
// 无输入则不搜索
if (searchValue.value === '') {
return (searchResult.value = [])
}
// 搜索
searchResult.value = data.filter((item) => item.includes(searchValue.value))
}
// 选中的item
const itemSelected = (item: string) => {
console.log(item)
}
// 输入框获取焦点
function focusFn() {
isFocus.value = true
}
// 输入框失焦
function blurFn() {
isFocus.value = false
}
//-----------------新增:高亮显示匹配的文本
function highlightedText(title: string) {
if (!searchValue.value) {
return title
}
// 创建正则表达式,用于查找并高亮显示匹配的部分
const regex = new RegExp(`(${searchValue.value})`, 'gi')
// 使用 replace 将匹配部分包裹在 <span> 中,并设置颜色
return title.replace(regex, '<span style="color: #1800D3;">$1</span>')
}
</script>
<style lang="less" scoped>
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: absolute;
left: 0;
color: black;
}
.search-input {
width: 230px;
}
.search-result {
display: flex;
width: 268px;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100px;
max-height: 200px;
overflow-y: scroll;
background: #fff;
border-radius: 12px;
.no-result {
width: 100%;
cursor: pointer;
text-align: center;
> span {
display: block;
padding: 12px 0;
}
}
.search-result-item {
width: 100%;
cursor: pointer;
text-align: center;
> span {
display: block;
padding: 12px 0;
}
}
.search-result-item:hover {
background: #ccc;
}
}
:deep(.ant-input-affix-wrapper) {
&-focused {
width: 268px;
}
}
::-webkit-scrollbar {
display: none;
}
</style>
第四步:键盘⬆️⬇️翻滚,Enter选中
首先,肯定需要增加一个监听键盘的事件: @keydown="handleKeydown"
然后,需要定义一个变量,判断这个变量跟选中的option的index,然后展示颜色动态加载颜色即可:
const defaultSelect = ref(0)
:class="defaultSelect === index ? 'search-options-item-selected' : ''"
完整代码如下:
xml
<template>
<div class="container">
<a-input
id="input"
class="search-input"
v-model:value="searchValue"
type="text"
placeholder="Search"
allow-clear
@change="onSearch"
@blur="blurFn"
@focus="focusFn"
@keydown="handleKeydown"
>
<template #prefix> <MonitorOutlined /> </template>
</a-input>
<div class="search-result" v-if="isFocus">
<!-- 有数据 -->
<div v-if="searchResult?.length">
<div class="search-result-item" v-for="(item, index) in searchResult" :key="index">
<span
@click="itemSelected(item)"
v-html="highlightedText(item)"
:class="defaultSelect === index ? 'search-options-item-selected' : ''"
></span>
</div>
</div>
<!-- 无数据 -->
<div class="no-result" v-else>
<span>No search results</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { MonitorOutlined } from '@ant-design/icons-vue'
import { ref } from 'vue'
// 搜索框输入数据
const searchValue = ref('')
// 搜索结果
const searchResult = ref()
// 输入框是否获取焦点
const isFocus = ref(false)
// 模拟数据库数据数据
const data = [
'richard',
'tom',
'lucy',
'jerry',
'jxxx',
'yyyj',
'ajax',
'exngj',
'jerry',
'lily',
'lucy',
'michael',
'张三',
'李四',
'王五',
'赵六',
]
// 获取搜索结果
const onSearch = () => {
// 无输入则不搜索
if (searchValue.value === '') {
return (searchResult.value = [])
}
// 搜索
searchResult.value = data.filter((item) => item.includes(searchValue.value))
}
// 选中的item
const itemSelected = (item: string) => {
console.log(item)
}
// 输入框获取焦点
function focusFn() {
//-----------------新增: 重置默认选中option
defaultSelect.value = 0
isFocus.value = true
}
// 输入框失焦
function blurFn() {
isFocus.value = false
}
// 高亮显示匹配的文本
function highlightedText(title: string) {
if (!searchValue.value) {
return title
}
// 创建正则表达式,用于查找并高亮显示匹配的部分
const regex = new RegExp(`(${searchValue.value})`, 'gi')
// 使用 replace 将匹配部分包裹在 <span> 中,并设置颜色
return title.replace(regex, '<span style="color: #1800D3;">$1</span>')
}
//-----------------新增: 键盘事件处理
const defaultSelect = ref(0)
const handleKeydown = (event: { key: any }) => {
switch (event.key) {
case 'ArrowUp':
if (defaultSelect.value > 0) {
defaultSelect.value--
const selectedItem = document.querySelector('.search-options-item-selected') as HTMLElement
selectedItem.scrollIntoView({ behavior: 'smooth', block: 'center' })
}
break
case 'ArrowDown':
if (defaultSelect.value < searchResult.value.length - 1) {
defaultSelect.value++
const selectedItem = document.querySelector('.search-options-item-selected') as HTMLElement
selectedItem.scrollIntoView({ behavior: 'smooth', block: 'center' })
}
break
case 'Enter':
itemSelected(searchResult.value[defaultSelect.value])
;(document.querySelector('#input') as HTMLInputElement)?.blur()
break
default:
break
}
}
</script>
<style lang="less" scoped>
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: absolute;
left: 0;
color: black;
}
.search-input {
width: 230px;
}
.search-result {
display: flex;
width: 268px;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100px;
max-height: 200px;
overflow-y: scroll;
background: #fff;
border-radius: 12px;
.no-result {
width: 100%;
cursor: pointer;
text-align: center;
> span {
display: block;
padding: 12px 0;
}
}
.search-result-item {
width: 100%;
cursor: pointer;
text-align: center;
> span {
display: inline-block;
padding: 12px 0;
width: 100%;
}
}
.search-result-item:hover {
background: #ccc;
}
}
.search-options-item-selected {
background: #ccc;
}
:deep(.ant-input-affix-wrapper) {
&-focused {
width: 268px;
}
}
::-webkit-scrollbar {
display: none;
}
</style>
注意:有个小坑
更加聪明的你又会发现,鼠标点击不起作用呀,选不到所需的option😢😢😢。
对喽,我们需要延迟关闭options从而给用户选中,在blurFn里面写如下代码:
javascript
// 输入框失焦
function blurFn() {
// 失焦延迟关闭,不然不会触发 onSelect
setTimeout(() => {
isFocus.value = false
}, 200)
}
小结
终于做完了,老板说做的不错,只要好好干以后肯定给我升职加薪。这把稳了!兄弟们学起来🏄🏄♂️🏄♀️
关注敲里个敲敲带你领略不一样的风采!