欢送关注我的公众号:前端侦探

最近在我的项目中须要做一个标签输入框,还挺实用的,演示成果如下

次要交互要求是这样的

  1. 点击输入框能够输出内容
  2. 按回车能够生成标签
  3. 按退格键能够删除标签
  4. 点击标签上的敞开按钮能够删除标签

习惯了各种 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;}

留神几点实现技巧:

  1. 标签的距离能够用 gap 实现
  2. 为了让输入框的区域铺满残余空间,这里用到了flex: auto
  3. 为了让父级处于聚焦状态,这里用到了: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 来行代码,这里总结一下实现要点

  1. 一般 div 元素输出纯文本能够应用 -webkit-user-modify: read-write-plaintext-only
  2. 一般 div 元素输出能够自适应内容宽度
  3. 一般 div 元素输入框的 placeholder 占位能够通过 :empty 联合伪元素实现
  4. 回车事件须要阻止默认事件,不然会换行
  5. 在一个元素的后面新增元素能够用 before 办法
  6. 删除一个元素的后面一个元素,能够用 previousElementSibling.remove 办法
  7. 给动静生成的元素绑定事件能够用事件委托的形式

在各种框架大行其道的气氛下,有些原生的属性和办法可能都不太关注了,这也不失为是一种损失。当然,我自身也是各种框架都会用,特地是大型、简单的交互页面,个别比拟小的交互,比方文章这个例子,在 ant design 中有相干的组件,也应用过,因为整体 UI 全是这种格调,设计也是依照这个设计的。起初须要独自开发一个 chrome 插件,也用到了这样一个交互,然而仅仅用了这样一个组件,引入整个框架就过于累赘了,所以还是抉择间接原生实现,简略不便。

最初,如果感觉还不错,对你有帮忙的话,欢送点赞、珍藏、转发❤❤❤

欢送关注我的公众号:前端侦探