textarea默认有滚动条,设置height: auto或者固定高度的话,书写体验又不够好;若使用div,设置contentEditable为true,兼容起来比较麻烦,而且这家伙复制粘贴时带着源格式,不好操作。 所以还是想办法使用textarea吧。

考虑文字换行等情况,这里使用 <pre> 符号,将textarea的值同步到 <pre> 中,通过这个看不见的 <pre> 盒子的高度自增长,撑高 autoHeight ,然后设置textarea绝对定位,top为0,覆盖<pre>的位置,且高度撑满 autoHeight (100%)。

js:

...
getInitialState() {
    return {
        text: 'react和css实现一个高度自适应的textarea'
    }
},
changeText(e) {
    this.setState({
        text: e.target.value
    });
},
render() {
    return <div className="autoHeight">
        <pre>{this.state.text}</pre>
        <textarea onChange={this.changeText}></textarea>
    </div>;
}
...

css:

.autoHeight {
    position: relative;

    pre{
        display: block;
        visibility: hidden;
        min-height: 10em;
        padding: 6px 7px; /*需要与textarea保持一致*/
        white-space: pre-wrap;
        line-height: 1.7em;
    }

    textarea {
        resize: none;
        position: absolute;
        height: 100%;
        top: 0;
        line-height: 1.7em;
    }
}

理论上,这样就可以实现目的了,但总有点瑕疵——pre和textarea的高度并非完全相等,于是,我将pre的visibility和textarea的top注释掉,对比两个在页面上的表现,发现:

  • pre 每段话是不换行的,横向一撑到底,相当于设置了white-space: no-wrap;
  • pre 每一段(即textarea中的return换行)的段落间距比较大(其实是line-height);
  • textarea 有预置的padding

于是改成:

pre{
    display: block;
    visibility: hidden;
    min-height: 10em;
    padding: 6px 7px; /*需要与textarea保持一致*/
    white-space: pre-wrap; /*按照textarea中的内容,该换行换行*/
    line-height: 1.7em; /*需要与textarea保持一致*/
}

textarea {
    resize: none;
    position: absolute;
    height: 100%;
    top: 0;
    line-height: 1.7em;
}

完美。

于是再一次打开lofter准备写点儿什么时,顺手审查了一下编辑框:

<body contenteditable="true" spellcheck="false" style="min-height: 170px;"></body>

难怪全屏写文章时光标经常就失踪了 (;´༎ຶД༎ຶ`) …