[聊天展示组件]浅尝“发送消息”背后的用户体验行为(重制)

很早的某一天,我对 Facebook 聊天行为做了一些研究,当时就觉得它对各方面的处理极具美观,比如聊天框、图片的处理、引用的处理、头像、状态的变化等等。这打破了我对“聊天功能太容易了”的固有想法。正好最近在写一个相关的项目,我决定封装一个聊天展示组件,且专注于聊天框。

目前已发布简单版本到GitHub:chat-ui ,欢迎star!
完全体聊天组件npm包还在优化和封装中,敬请期待~

[聊天展示组件]浅尝“发送消息”背后的用户体验行为(重制)_第1张图片

如何处理单张图片

你可能会认为处理不同大小和宽高比的图像所需的工作是一项简单的任务。很多人似乎会这么做:max-width: 100% ?实际情况并非如此。

[聊天展示组件]浅尝“发送消息”背后的用户体验行为(重制)_第2张图片

当用户上传图像时,其宽度被添加为内联CSS,并且利用padding-bottom(高宽比,现在被广泛应用在元素的自身响应式中)处理图像的响应性。


    

css如下:

.image {
    display: block;
    border-radius: 18px;
    border: 1px solid #0006;
    overflow: hidden;
}

.text {
    display: flex;
    align-items: center;
    height: 100%;
    padding: 3px;
    border-radius: 9px;
    background-color: #4397f7;
    border: 1px solid #4397f7;
    color: white;
}

.image__main {
    max-width: 32rem;
}

.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的一点就在于,用 padding-bottom 打了个底,然后用 position 铺上图片,好处是图片本身随响应式而动,但响应式的不是图片本身。也就是:宽高比是根据所使用的图像动态生成的。

除此之外,重要的是要记住,flexbox不会将flex项目缩小到其最小内容大小以下。这意味着,如果将浏览器大小调整为特定宽度,flex 项将不会缩小到低于其最小内容的。要解决此问题,我们需要添加 min-width: 0。否则,对图片来讲,绝对会出现溢出的情况。

聊天html的基本架子如下:

其中,flex 项是message__outer的直接子项,我们为其添加message__row

.message__row {
    min-width: 0;
    /* other styles */
}

在笔者和别人讨论时,有人会争论为什么要获取图像的固定宽度并将其添加为内联CSS?其实很容易想到这与图像的加载有关。否则,如果我们没有为图像设置固定大小,我们将注意到布局的变动。

如何处理多个图像

如果一次性发送多张图片,则不需要考虑它们的宽高比。相反,我们可以将它们作为“一条消息”。每个图像将包含在一个正方形中,并利用object-fit避免扭曲或拉伸它们。
[聊天展示组件]浅尝“发送消息”背后的用户体验行为(重制)_第3张图片

.gallery {
    display: flex;
    flex-wrap: wrap;
    flex: 1;
    /* 回位使与其同级对齐. */
    margin: -2px;
}

.gallery__item {
    padding: 1px;
}

.gallery__item--third {
    flex: 33.33%;
}

.gallery__item--half {
    flex: 50%;
}

简单来说,我们需要这么做:

  • 图像网格也是使用 CSS flexbox 构建的。
  • 根据图像的数量,图像的宽度值等于其 flexbox 容器的1/3或1/2(有最多可发送图片数量)。
  • 间距通过每个图像父级上的填充进行处理。
  • 为避免在库的边缘周围出现不必要的间距,应在 flex 容器上使用等于padding的负边距值。

我想强调每个项目之间的间距的使用。在这种情况下,padding: 1px 非常有用,因为它在LTR和RTL方向上都有效。
其中,最重要的还是如何高性能的判断图片的展示结构了 —— 虽然不知道 Facebook团队是如何处理图片和文字消息的。但我在组件封装中选择将两者分开,专心打造一个图片选择器组件。目前还在设计中。

如何处理文字消息

除了图片,Facebook对于文字消息的展示也是“别具一格”。具体表现在单条消息、连续多条消息、混合消息、引用消息的样式都有不同。
比如单条消息时是这样的:
[聊天展示组件]浅尝“发送消息”背后的用户体验行为(重制)_第4张图片

而连续发送多条消息时,基本是这种状态:
[聊天展示组件]浅尝“发送消息”背后的用户体验行为(重制)_第5张图片

而且,用户体验并非止步于此。如果你使用的是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;
    flex-shrink: 0;
}
.message__spacer {
    flex: 1;
    text-align: center;
}

其中actions是操作菜单。笔者在这里添加固定宽度的原因是:

  • 为其保留空间以避免布局偏移。
  • 更好地控制内部布局。例如,聊天气泡应具有max-width,我们可能需要从中扣除操作菜单宽度。

为了确保在较小的尺寸上不会出现问题,我们需要注意以下几点:

  • 考虑到默认宽度因空间不足而坍塌的情况;
  • 防止消息操作以较小的大小收缩;
  • 通过打破很长的单词来避免溢出;
.message__bubble {
    max-width: calc(100% - 67px);
    overflow-wrap: break-word;
}
.message__actions {
    flex-shrink: 0; //这个上面代码中有了
}

对于文字消息来说,由于要考虑语种的问题,我们可以为其加上dir属性值:

如果第一个文本是用RTL语言编写的(例如:阿拉伯语),则文本将从右到左阅读,即使你在此之后输入了LTR文本也是如此。
如果以 LTR 格式输入文本(例如:英语),则文本将从左到右阅读。

[聊天展示组件]浅尝“发送消息”背后的用户体验行为(重制)_第6张图片

对于上面说的多个气泡不同的圆度状态的问题,我们还要注意到对于发送方和接收方而言,它们会有所不同。除此之外,它们将根据页面方向翻转(LTR与RTL)。

我在这里采用了 border-radius 的“双精度写法”。因为在布局发生变化时,内联开始和内联结束将翻转。这样,我们就不必关心其方向。

对于文字消息来说,需要有操作按钮用以进行撤回等行为。就像这样:
6

我采用了这样的手段:

  • 它的右侧有填充,以防您发送消息。否则,填充位于左侧;
  • 它通过flex-direction: row-reverse和发送方消息的默认顺序进行翻转;

如果消息太长,或者是图像或视频,则操作菜单应垂直居中。这可以通过使用弹性框对齐来实现。

.message__actions {
    // 略,上面有

    .menu {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        visibility: hidden;
    }
}

当邮件具有长文本或图像时,元素将拉伸以填充其父高度。

说到这了,有一个并没有说到但是似乎很容易就想到的问题:间隔元素是干嘛的?方便翻转!

如何主题色(组件待增)

怎么能没有主题色功能呢?之前看 Facebook 的工程师似乎对在气泡上加渐变色非常看好。而且实现手段非“css自定义变量”这种新技术。我尝试了一下应该是这么干的:
假如说我们有这样的背景色:

.messages-parent {
    background-color: #3a12ff;
    background-image: linear-gradient(#faaf00, #ff2e2e, #3a12ff);
}

对,没错。就是对整个父元素添加的。
[聊天展示组件]浅尝“发送消息”背后的用户体验行为(重制)_第7张图片

然后从每个邮件气泡中删除背景色和边框半径。

[聊天展示组件]浅尝“发送消息”背后的用户体验行为(重制)_第8张图片

下一步,我们需要将白色背景添加到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;
}

[聊天展示组件]浅尝“发送消息”背后的用户体验行为(重制)_第9张图片

现在有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; /** !!! **/
}

最后我们应当借助background-attachment因为我们需要让颜色在滚动中相对保持。

.messages-parent {
    //...
    background-attachment: fixed;
}

你可能感兴趣的:([聊天展示组件]浅尝“发送消息”背后的用户体验行为(重制))