原生CSS与JS实现自适应标签输入框:布局、交互与代码详解

作者:竹影时间:2026-10-25 08:38:35浏览:169

本文详解如何使用原生CSS和JavaScript实现一个自适应的标签输入框。通过Flex布局实现标签与输入框的自适应排列,利用contenteditable属性解决Input宽度限制问题,并演示如何结合JS监听回车、退格及点击事件完成标签的创建与删除,无需引入重型框架即可满足轻量级交互需求。

近期在项目开发中遇到一个常见需求:实现一个实用的标签输入组件,具体交互效果如下所示:

一、自适应输入框布局

该组件的核心交互逻辑包含以下几个方面:

  • 用户点击输入区域即可开始输入文本内容。
  • 按下回车键(Enter)后,当前输入内容将转换为一个独立的标签。
  • 按下退格键(Backspace)时,若输入框为空,则删除前一个已生成的标签。
  • 点击标签右侧的关闭图标,可直接移除该特定标签。

在各类 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;
}

在布局实现中,有几个关键点需要注意:

  • 标签之间的间距可以通过 CSS 的 gap 属性轻松实现。
  • 为了让输入框区域自动占据剩余空间,这里使用了 flex: auto 属性。
  • 为了保持父容器在输入框聚焦时的视觉状态,使用了 :focus-within 伪类。

此时的布局效果如下:

一、自适应输入框布局

然而,直接使用原生 input 元素存在局限性,具体表现如下:

一、自适应输入框布局

由于标准 input 元素的宽度不随内容自适应,当输入文字较多时,会出现文字被截断且无法换行的问题:

一、自适应输入框布局

理想的交互应当是:当输入内容超出宽度时,整体自动换行。如何实现这一效果?我们可以使用普通的 div 元素替代 input。

<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元素只能输入纯文本[1]。

一、自适应输入框布局

通过上述方式,即可实现内容宽度的自适应。

二、输入框占位提示

由于我们将 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;
}

通过添加这一伪类选择器,即可解决该需求。

一、自适应输入框布局

这两种占位逻辑均符合用户认知,具体采用哪种取决于设计稿的要求。

三、标签的输入与删除

实现标签的创建与删除需要 JavaScript 介入,主要监听键盘的“回车”和“退格”两个键值。需要注意的是,默认情况下,普通 contenteditable 元素在按下回车时会触发换行行为,如下所示:

一、自适应输入框布局

因此,在监听键盘事件时,必须阻止默认行为(preventDefault),然后动态创建标签元素,并通过 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(); // 需要判断前一个元素是否存在
  }
})

其次是点击删除图标的删除。由于标签是动态生成的 DOM 节点,这里需要采用事件委托的方式添加删除事件监听器。

// TagContent是父级容器
TagContent.addEventListener('click', function(ev) {
  if (ev.target.className === 'tag-close') {
    ev.target.parentNode.remove();
  }
  TagInput.focus(); //点击任意地方输入框都需要聚焦
})

至此,文章开头所示的完整交互效果已实现:

一、自适应输入框布局

四、选择框架还是原生

总结以上实现过程。

整体实现并不复杂,许多交互逻辑甚至可以通过纯 CSS 轻松完成,JavaScript 代码量仅十余行。这里总结关键实现要点:

  • 普通 div 元素实现纯文本输入,可使用 -webkit-user-modify: read-write-plaintext-only
  • 普通 div 元素实现输入框时,可自适应内容宽度
  • 普通 div 元素实现 placeholder 占位,可通过 :empty 结合伪元素 ::before 实现
  • 回车事件必须阻止默认行为,否则会导致意外换行
  • 在元素前方新增元素,可使用 before 方法
  • 删除元素前方的兄弟节点,可使用 previousElementSibling.remove 方法
  • 为动态生成的元素绑定事件,推荐使用事件委托方式

在各类前端框架盛行的当下,许多原生属性和方法逐渐被忽视,这 arguably 是一种技术损失。当然,我本人也熟练使用各种框架,特别是在处理大型、复杂交互页面时。然而,对于如本文示例这般简单的交互,虽然 Ant Design 等库提供了现成组件,但若仅为此引入整个框架,则显得过于累赘。例如在开发 Chrome 插件时,若仅需此单一交互组件,直接原生实现显然更为简单高效。

以上就是基于原生CSS+JS实现一个标签输入框的详细内容,更多关于JS标签输入框的资料请关注本站其它相关文章!

作者最新文章