共计 3923 个字符,预计需要花费 10 分钟才能阅读完成。
欢送关注我的公众号:前端侦探
最近在我的项目中须要做一个标签输入框,还挺实用的,演示成果如下
次要交互要求是这样的
- 点击输入框能够输出内容
- 按回车能够生成标签
- 按退格键能够删除标签
- 点击标签上的敞开按钮能够删除标签
习惯了各种 react 框架或者 UI 库,大家有多久没接触没有原生开发了呢?有时候页面比较简单,没必要引入一个残缺的框架,原生实现就齐全满足了,一起看看吧
一、自适应输入框布局
不论什么组件,布局都是最重要的。这个布局分为标签和输入框两个局部,假如 HTML 如下
<div class="tags-content">
<tag>CSS<a class="tag-close"></a></tag>
<input class="tags-input" placeholder="增加标签">
</div>
简略润饰一下
.tags-content{
display: flex;
flex-wrap: wrap;
align-items: flex-start;
gap: 6px;
width: 400px;
box-sizing: border-box;
padding: 8px 12px;
border: 1px solid #D9D9D9;
border-radius: 4px;
font-size: 16px;
line-height: 24px;
color: #333;
outline-color: #4F46E5;
overflow: auto;
cursor: text;
}
tag{
display: flex;
align-items: center;
padding: 4px 0 4px 8px;
font-size: 16px;
line-height: 24px;
background: #F5F5F5;
color: rgba(0, 0, 0, 0.85);
cursor: default;
}
tag-close{
width: 18px;
height: 18px;
cursor: pointer;
background: url("data:image/svg+xml,%3Csvg width='10'height='10'viewBox='0 0 10 10'fill='none'xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M5.578 5l2.93-3.493a.089.089 0 0 0-.068-.146h-.891a.182.182 0 0 0-.137.064l-2.417 2.88-2.416-2.88a.178.178 0 0 0-.137-.064h-.89a.089.089 0 0 0-.069.146L4.413 5l-2.93 3.493a.089.089 0 0 0 .068.146h.89a.182.182 0 0 0 .138-.064l2.416-2.88 2.417 2.88c.033.04.083.064.137.064h.89a.089.089 0 0 0 .069-.146l-2.93-3.493z'fill='%23000'fill-opacity='.45'/%3E%3C/svg%3E") center no-repeat;
}
.tags-input{
flex: auto;
border: 0;
outline: 0;
padding: 4px 0;
line-height: 24px;
font-size: 16px;
}
.tags-content:focus-within,
.tags-content:active{outline: auto #4F46E5;}
留神几点实现技巧:
- 标签的距离能够用
gap
实现 - 为了让输入框的区域铺满残余空间,这里用到了
flex: auto
- 为了让父级处于聚焦状态,这里用到了
:focus-within
成果如下
然而这里的输入框用 input 还是有些问题的,如下所示
因为 input 输出内容无奈追随宽度自适应,所以有时候会呈现文字被截断的状况
现实状况下,当输出内容较多时,应该整体换行。如何实现呢?能够用一般的 div 来实现
<div class="tags-content">
<tag>CSS<a class="tag-close"></a></tag>
<div class="tags-input" placeholder="增加标签"></div>
</div>
能够通过增加 contenteditable
或者以下 CSS 来实现
.tags-input{-webkit-user-modify: read-write-plaintext-only;}
这个属性示意只容许输出纯文本,有趣味的能够参考张鑫旭的这篇文章:小 tip: 如何让 contenteditable 元素只能输出纯文本
这样能够自适应内容宽度了
二、输入框占位提醒
因为输入框曾经从 input 换成了一般的 div 标签,并没有 placeholder 个性。不过,咱们依然能够通过其余 CSS 个性来实现占位成果,当输入框没有内容时,就能够匹配到 :empty
选择器,而后通过伪元素 ::before
动静生成 placeholder 内容,具体实现如下
.tags-input:empty::before{content: attr(placeholder);
color: #828282;
}
成果如下
这样就简直和 input 的占位成果统一了
另外还有一种状况,如果须要 仅在没有任何标签的状况下才显示占位,如何实现呢?能够想想,在没有任何标签的状况下,HTML 就变成了这样
<div class="tags-content">
<div class="tags-input" placeholder="增加标签"></div>
</div>
这种状况,就仅剩输入框惟一元素了 ,惟一元素能够通过only-child
来匹配,所以实现如下
.tags-input:only-child:empty::before{content: attr(placeholder);
color: #828282;
}
这样增加一个伪类就解决了
两种需要都合乎认知,看设计如何决定了
三、标签的输出与删除
要实现标签的输出与删除就须要 JS 出马了,只须要监听键盘的“回车”和“退格”两个键值。须要留神的是,默认状况下,一般 contenteditable
元素在回车时,会呈现换行,如下
因而,在监听键盘事件时须要阻止默认事件,而后动态创建标签元素,通过 before
增加到输入框后面,具体实现如下
// TagInput 是输入框
TagInput.addEventListener('keydown', function(ev) {if (ev.key === 'Enter') {ev.preventDefault()
if (this.innerText) { // 输入框内容通过 innerText 获取
const tag = document.createElement('TAG');
tag.innerHTML = this.innerText + '<a class="kalos-tag-close"></a>';
this.before(tag);
this.innerText = '';
}
}
})
这样就能失常创立标签了
而后是标签的删除。
这里有两种路径,首先看键盘的删除,具体逻辑是当输入框内容为空时删除标签,很简略,删除的标签就是输入框的后面一个元素,通过 previousElementSibling
获取,具体实现如下
TagInput.addEventListener('keydown', function(ev) {if (ev.key === 'Backspace' && !this.innerText) {this.previousElementSibling?.remove(); // 须要判断前一个元素是否存在
}
})
而后是点击删除图标的删除。因为标签是动静生成的,所以这里须要用事件委托的形式来增加删除事件
// TagContent 是父级容器
TagContent.addEventListener('click', function(ev) {if (ev.target.className === 'tag-close') {ev.target.parentNode.remove();
}
TagInput.focus(); // 点击任意中央输入框都须要聚焦})
这样就实现了文章结尾的所示成果
残缺代码能够拜访:input-tag
四、抉择框架还是原生?总结一下
整体实现并不算简单,不少交互逻辑 CSS 也能够轻松实现,JS 也就 10 来行代码,这里总结一下实现要点
- 一般 div 元素输出纯文本能够应用 -webkit-user-modify: read-write-plaintext-only
- 一般 div 元素输出能够自适应内容宽度
- 一般 div 元素输入框的 placeholder 占位能够通过 :empty 联合伪元素实现
- 回车事件须要阻止默认事件,不然会换行
- 在一个元素的后面新增元素能够用 before 办法
- 删除一个元素的后面一个元素,能够用 previousElementSibling.remove 办法
- 给动静生成的元素绑定事件能够用事件委托的形式
在各种框架大行其道的气氛下,有些原生的属性和办法可能都不太关注了,这也不失为是一种损失。当然,我自身也是各种框架都会用,特地是大型、简单的交互页面,个别比拟小的交互,比方文章这个例子,在 ant design 中有相干的组件,也应用过,因为整体 UI 全是这种格调,设计也是依照这个设计的。起初须要独自开发一个 chrome 插件,也用到了这样一个交互,然而仅仅用了这样一个组件,引入整个框架就过于累赘了,所以还是抉择间接原生实现,简略不便。
最初,如果感觉还不错,对你有帮忙的话,欢送点赞、珍藏、转发❤❤❤
欢送关注我的公众号:前端侦探