最近在开发 IM 产品时,需要改造聊天输入框并参考飞书的交互效果。初看到这个自动换行变高的效果,第一反应是需要通过 JavaScript 动态计算来实现。然而使用 AI 辅助开发后,效果并不理想。于是我查看了飞书网页版的源码,意外发现这个看似复杂的效果竟然是通过纯 CSS 的 Flex 布局实现的。

通过巧妙运用 flex-wrapflex-automargin-left: auto 这三个关键属性,不仅代码更加简洁优雅,性能表现也远超 JavaScript 方案。

效果动图

效果说明

当输入框内容较少时,输入框和按钮组在同一行显示:

1
[输入框........................] [按钮组]

当输入框内容增多需要换行时,由于 flex-wrap 的作用,按钮组会自动换到下一行,并通过 margin-left: auto 保持右对齐:

1
2
3
[输入框内容第一行.....................]
[输入框内容第二行.....................]
[按钮组]

效果体验

核心原理

这个效果的核心在于巧妙地运用了 Flex 布局的三个关键属性:

  1. flex-wrap: 允许 flex 项目换行
  2. flex-auto: 让输入框自动占据可用空间
  3. margin-left: auto: 将按钮组推到右侧

完整代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Flex 自动变高输入框</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="p-10">
<div class="flex flex-row flex-wrap border rounded-md w-[600px] pb-2">
<!-- 输入框 -->
<div
contenteditable
class="relative overflow-y-scroll flex-auto max-h-[100px] has-[div+div]:w-full m-2 mb-0 outline-none"
placeholder="请输入消息..."
></div>

<!-- 按钮组 -->
<div class="flex flex-nowrap items-center ml-auto mr-3 mt-2 gap-3">
<button title="插入图片">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect width="18" height="18" x="3" y="3" rx="2" ry="2" />
<circle cx="9" cy="9" r="2" />
<path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" />
</svg>
</button>
<button title="插入视频">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect width="18" height="18" x="3" y="3" rx="2" />
<path d="M7 3v18" />
<path d="M3 7.5h4" />
<path d="M3 12h18" />
<path d="M3 16.5h4" />
<path d="M17 3v18" />
<path d="M17 7.5h4" />
<path d="M17 16.5h4" />
</svg>
</button>
<button title="发送消息">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path
d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"
/>
<path d="m21.854 2.147-10.94 10.939" />
</svg>
</button>
</div>
</div>
</body>
</html>