我确实对Facebook聊天行为做了一些研究。并且我觉得它对各方面的处理极具美观,比如聊天框、图片的处理、引用的处理、头像、状态的变化等。这打破了我“聊天功能太容易了”的固有想法。
处理单张图片
你可能会认为处理不同大小和宽高比的图像所需的工作是一项简单的任务。很多人似乎会这么做:max-width: 100%
?Facebook的情况并非如此。
当用户上传图像时,其宽度被添加为内联CSS,并且利用padding-bottom
(高宽比,现在被广泛应用在元素的自身响应式中)处理图像的响应性。
.image {
display: block;
border-radius: 18px;
border: 1px solid #0006;
overflow: hidden;
}
.image__main {
max-width: 480px;
}
.image__element {
max-width: 100%;
position: relative;
}
.image__aspectRatio {
position: relative;
}
.image__wrapper {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
}
.image__wrapper img {
display: block;
max-width: 100%;
max-height: 200px;
width: 100%;
height: 100%;
}
以下是每个图像应遵循的规则:
- 最大宽度为480px
- 最大高度为200px
- 调整大小时,图像应保持相同的纵横比
- 如果图像小于最大值,则按原样显示它
这里非常nice的一点就在于,宽高比是根据所使用的图像动态生成的。
除此之外,重要的是要记住,flexbox不会将flex项目缩小到其最小内容大小以下。这意味着,如果将浏览器大小调整为特定宽度,flex 项将不会缩小到低于其最小内容大小。要解决此问题,我们需要添加 min-width: 0
。否则,对图片来讲,绝对会出现溢出的情况。
这里若要更进一步研究,请参考笔者上一篇文章:用户体验思考与flex三坑:元素不均分、溢出不省略和垂直不滚动
大的架子基本代码如下:
其中,flex 项是message__outer
的直接子项,我们为其添加message__row
。
.message__row {
min-width: 0;
/* other styles */
}
在笔者和别人讨论时,有人会争论为什么要获取图像的固定宽度并将其添加为内联CSS?其实很容易想到这与图像的加载有关。否则,如果我们没有为图像设置固定大小,我们将注意到布局的变动。
处理多个图像
如果一次性发送多张图片,则不需要考虑它们的宽高比。相反,Facebook将它们作为“一条消息”。每个图像将包含在一个正方形中,并利用object-fit
避免扭曲或拉伸它们。
.gallery {
display: flex;
flex-wrap: wrap;
flex: 1;
/* Revert the padding to make the gallery aligned
with its siblings. */
margin: -2px;
}
.gallery__item {
/* Add a 2px offset around each image, will result in 4
for two adjacent images. */
padding: 2;
}
.gallery__item--third {
flex: 33.33%;
}
.gallery__item--half {
flex: 50%;
}
简单来说,我们需要这么做:
- 图像网格也是使用 CSS flexbox 构建的。
- 根据图像的数量,图像的宽度值等于其 flexbox 容器的1/3或1/2(有最多可发送图片数量)。
- 间距通过每个图像父级上的填充进行处理。
- 为避免在库的边缘周围出现不必要的间距,应在 flex 容器上使用等于
padding
的负边距值。
我想强调每个项目之间的间距的使用。在这种情况下,padding: 2px
甚至更有用,因为它在LTR和RTL方向上都有效。
其中,最重要的还是如何高性能的判断图片的展示结构了 —— 虽然不知道Facebook团队是如何处理图片和文字消息的。但是如果是我,必然是将两者分开,专心打造一个图片选择器组件。这个还在设计中,我似乎考虑了太多以至于难以下手。
文字消息
除了图片,Facebook对于文字消息的展示也是“别具一格”。具体表现在单条消息、连续多条消息、混合消息、引用消息的样式都有不同。
比如单条消息时是这样的:
而连续发送多条消息时,基本是这种状态:
而且,用户体验并非止步于此。如果你使用的是LTR
(从左到右,例如英语)布局,那么发送方是蓝色气泡,接收方是灰色气泡(在你这边看);如果布局是RTL
(从右到左,如阿拉伯语),则反之亦然。
当然还要以上面提到的“大的架子”HTML结构为基准。它的css是这样的:
.message__outer {
display: flex;
}
.message__inner {
flex: 1;
display: flex;
flex-direction: row-reverse;
}
.message__actions {
width: 67px;
padding-right: 5px;
}
.message__spacer {
flex: 1;
}
其中actions
是操作菜单。笔者认为在这里添加固定宽度的原因是:
- 为其保留空间以避免布局偏移。
- 更好地控制内部布局。例如,聊天气泡应具有
max-width
,我们可能需要从中扣除操作菜单宽度。
为了确保在较小的尺寸上不会出现问题,我们需要注意以下几点:
- 考虑到默认宽度因空间不足而坍塌的情况;
- 防止消息操作以较小的大小收缩;
- 通过打破很长的单词来避免溢出;
.message__bubble {
max-width: calc(100% - 67px);
overflow-wrap: break-word;
}
.message__actions {
flex-shrink: 0;
}
对于文字消息来说,由于要考虑语种的问题,我们可以为其加上dir
属性值:
如果第一个文本是用RTL
语言编写的(例如:阿拉伯语),则文本将从右到左阅读,即使你在此之后输入了LTR
文本也是如此。
如果以 LTR
格式输入文本(例如:英语),则文本将从左到右阅读。
对于上面说的多个气泡不同的圆度状态的问题,我们还要注意到对于发送方和接收方而言,它们会有所不同。除此之外,它们将根据页面方向翻转(LTR与RTL)。
/* 设置特殊角弧度,一般是18px,特殊角是4px */
/* You, blue messages */
/* First message */
.message--first .message__bubble {
border-end-end-radius: 4px;
}
/* Middle message */
.message--middle .message__bubble {
border-start-end-radius: 4px;
border-end-end-radius: 4px;
}
/* Last message */
.message--last .message__bubble {
border-start-end-radius: 4px;
}
这里用到了css某些属性的“双精度写法”。因为在布局发生变化时,内联开始和内联结束将翻转。这样,我们就不必关心其方向 —— 所以,其实你还可以用这样的写法:border-radius: 左上 右上 右下 左下;
对于文字消息来说,需要有操作按钮用以进行撤回等行为。就像这样:
Facebook采用了这样的手段:
- 它的右侧有填充,以防您发送消息。否则,填充位于左侧;
- 它通过
flex-direction: row-reverse
和发送方消息的默认顺序进行翻转;
如果消息太长,或者是图像或视频,则操作菜单应垂直居中。这可以通过使用弹性框对齐来实现。
.message__actions {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 67px;
padding-right: 5px;
}
当邮件具有长文本或图像时,元素将拉伸以填充其父高度。
注意,这里是“垂直居中”,而并非“垂直排列”。这一点让我有些费解。这个时候变为垂直排列难道不会更好一点么?
说到这了,有一个并没有说到但是似乎很容易就想到的问题:间隔元素是干嘛的?方便翻转!
最后,作为Facebook,怎么能没有主题色功能呢?他们的工程师似乎对在气泡上加渐变色非常看好,虽然在我看来,,,一言难尽。但是非常牛逼!
这是通过css完成的!笔者一开始以为是依靠类似滚动视差或者纯js实现的,但后来发现我小了,格局小了~
假如说我们有这样的背景色:
.messages-parent {
background-color: #3a12ff;
background-image: linear-gradient(#faaf00, #ff2e2e, #3a12ff);
}
对,没错。就是对整个父元素添加的。
然后从每个邮件气泡中删除背景色和边框半径。
下一步,我们需要将白色背景添加到UI中除聊天气泡之外的所有内容。
.message__actions,
.message__status,
.spacer-xs,
.spacer-lg,
.message__spacer,
.message__avatar {
background-color: #fff;
}
现在我们已经处理了聊天气泡,我们如何将圆角添加回去?伪元素!!!
.custom-theming .message__bubble {
position: relative;
border-radius: 0;
background: transparent;
}
.custom-theming .message__bubble::after {
content: "";
position: absolute;
left: -36px;
right: -36px;
top: -36px;
bottom: -36px;
border: 36px solid #fff;
}
现在有border
了,但是里面的部分还是直角。接下来,我们需要编辑内圆角。众所周知,它有一个公式:内半径 = 边框半径 - 边框宽度
这意味着,即使加上上述内容,内角仍将为零。如果没有自定义主题,边框半径为border-radius: 36px 18px
。为了在内半径上反映这一点,我们需要给伪元素添加border-radius
。使得内半径 = 54px - 36px = 18px
于是有:
.custom-theming .message__bubble {
position: relative;
border-radius: 0;
background: transparent;
}
.custom-theming .message__bubble::after {
content: "";
position: absolute;
left: -36px;
right: -36px;
top: -36px;
bottom: -36px;
border-radius: 54px;
border: 36px solid #fff;
}
最后,我们需要在消息气泡范围内限制伪元素大小。
.custom-theming .message__bubble {
position: relative;
border-radius: 0;
background: transparent;
overflow: hidden; /** !!! **/
}
最后一步是将border-color
更改为白色,这里我们确实应当借助background-attachment
因为我们需要让颜色在滚动中相对保持。
.messages-parent {
background-color: #3a12ff;
background-image: linear-gradient(#faaf00, #ff2e2e, #3a12ff);
background-attachment: fixed;
border-left: 2px solid #fff;
borfer-right: 2px solid #fff;
}
最后的最后,拜拜~