使用react hooks实现微信聊天输入框组件

3,493 阅读2分钟

先看一下效果

微信聊天输入框的特点

  1. 随着文字输入的增加,输入框的行数增加
  2. 输入框最大5行
  3. 超过5行则开始滚动

实现方式

使用textarea标签,通过设置rows属性来更改输入行数

难点分析

  1. 如何检测文字到达边界,改变textarea的rows属性

如何解决

使用相同宽度的textarea,将该textarea设置为不可见,rows属性为1,readonly。

通过textarea的clientHeight和ScrollHeight来判断行数。

其它注意点

  1. 去除文字首尾的空白字符
  2. 去除空白字符后无其它字符,则不允许发送

代码实现

// InputArea.js

import React, { useState, useMemo, useRef, useEffect } from 'react'

import './InputArea.scss'

export default function InputArea(props) {
  const [content, setContent] = useState('')

  // 默认textarea的最高行数
  const [defaultMaxRows] = useState(5)

  const [rows, setRows] = useState(1)

  const hiddenTextarea = useRef(null)

  const maxRows = useMemo(() => props.maxRows || defaultMaxRows, [props.maxRows])

  useEffect(() => {
    return () => {
      let r = hiddenTextarea.current.scrollHeight / hiddenTextarea.current.clientHeight
      if (r > maxRows) r = maxRows
      setRows(r)
    }
  }, [maxRows, content])

  // 用户输入内容是否可以发送
  // 去除输入内容的收尾两端空格
  const disable = useMemo(() => content.replace(/(^\s+)|(\s+$)/g, '') === '', [content])

  // 发送消息
  function sendMessage() {
    // 调用接口发送消息
    props.sendMessage && props.sendMessage()

    // 发送成功后
    setContent('')
  }

  function onChange(e) {
    // console.log('onchange', e.target.value)
    setContent(e.target.value)
  }

  function onBlur(e) {
    // 键盘收起
    setTimeout(() => {
      // safari on ios9 一下不支持window.scrollTo 好在这个兼容性问题出现在ios12的微信里
      window.scrollTo && window.scrollTo(0, 99999999)
    }, 100)
  }

  function onFocus(e) {
    // 键盘弹出
    setTimeout(() => {
      // safari on ios9 一下不支持window.scrollTo 好在这个兼容性问题出现在ios12的微信里
      window.scrollTo && window.scrollTo(0, 99999999)
    }, 100)
  }

  return (
    <div className="m-input-area__wrapper">
      <div className="m-input-area__content">
        <textarea
          spellCheck={false}
          placeholder="输入聊天内容..."
          rows={rows}
          value={content}
          onChange={onChange}
          onBlur={onBlur}
          onFocus={onFocus}
        />

        {/* 不可见的输入框 */}
        <textarea readOnly rows={1} value={content} ref={hiddenTextarea} />
      </div>
      <input
        className={`m-input-area__btn ${disable ? 'disable' : ''}`}
        type="button"
        value="发送"
        disabled={disable}
        onClick={sendMessage}
      />
    </div>
  )
}

.m-input-area__wrapper {
  display: flex;
  justify-content: space-between;
  background: #F6F6F6;
  padding: 3px 6px 4px 6px;
  // border-top: solid 1px #f1f1f1;
  box-shadow: 0 -1px 4px #ddd;
}

.m-input-area__content {
  flex-grow: 1;
  position: relative;
  // height: fit-content;
  border-radius: 2px;
  margin-right: 6px;
  padding: 4px 6px;
  background: #fff;
  // font-size: 0; // 解决textarea后面会添加空白字符 导致的高度无端撑开3px 其它解决方式看笔记

  textarea {
    display: block; // 解决textarea后面会添加空白字符 导致的高度无端撑开3px 其它解决方式看笔记
    font-size: 14px;
    padding: 0;
    border: 0;
    line-height: 22px;
    color: #71777c;
    resize: none; // 不允许调整元素大小
    width: 100%;
    outline: none;

    &::placeholder {
      color: #CECECE;
    }

    &:nth-child(2) {
      visibility: hidden;
      position: absolute;
      left: 0;
      top: 0;
      padding: 0 6px;
      box-sizing: border-box;
    }
  }
}

.m-input-area__btn {
  width: 60px;
  height: 30px;
  font-size: 14px;
  // text-align: center;
  color: #fff;
  background: #007fff;
  // border: solid 1px #007fff;
  border: none;
  border-radius: 2px;
  font-weight: 600;
  cursor: pointer;
  letter-spacing: 4px;
  text-indent: 4px;

  &.disable {
    // border-color: #CECECE;
    background: #cecece;
    color: #fff;
  }
}