输入框与显示框

之前做一个项目的时候需要实现怎么一个场景,有一个做题网站,需要有一个输入框和一个显示框。
输入框可以给用户输入简答题答案,为了让用户有更好的输入体验,需要可以支持 换行符,并且在数据的数据行数超过原来设定好的输入框的高度时,输入框的 高度自动增加,让用户能在屏幕上尽量多的看到其输入的数据。
而之后用户查看答案时直接把按其输入时的排版显示在显示框中(显示框的高度由要显示的内容行数决定)

试验一

1. 显示框:

显示框比较简单,只要我们给显示数据的 div 元素添加 css 使其能像 textarea 那样显示文字排版就像,具体实现如下:

<style>
.answer {
  white-space: pre-wrap;       /* 使该div能显示换行符: ↵ , 并且自动换行*/
  word-wrap: break-word;       /* textarea 默认的表现是这样的 */
  /* word-break: break-all;*/   /* 强制英语单词换行 */
}
</style>
<body>
    <div class="answer"></div>
</body>
<style>
.answer {
  white-space: pre-wrap;       /* 使该div能显示换行符: ↵ , 并且自动换行*/
  word-wrap: break-word;       /* textarea 默认的表现是这样的 */
  /* word-break: break-all;*/   /* 强制英语单词换行 */
}
</style>
<body>
    <div class="answer"></div>
</body>

2. 输入框:

一般需要输入较多文字时,我们一般会使用到 textarea 标签,但由于使用 <textarea> 标签无法自动在文字内容超过输入框的高度时自动增加而是显示滚动条,所以我们就直接不使用 textarea 了。改用 contenteditable 属性,将一个 div 元素赋上此属性后就可以在这个 div 中输入文字,它表现得就像是一个 textarea 一样,只是它在输入内容超过其高度时高度会自增。具体实现如下:

<style>
    .input-box {
        width: 250px;
        min-height: 150px;
        border: 1px solid black;
    }
</style>

<body>
    <div class="input-box" contenteditable ></div>  <!-- 使改div能编辑,并且编辑的时候能 换行 和 高度自增 -->
</body>

<style>
    .input-box {
        width: 250px;
        min-height: 150px;
        border: 1px solid black;
    }
</style>

<body>
    <div class="input-box" contenteditable ></div>  <!-- 使改div能编辑,并且编辑的时候能 换行 和 高度自增 -->
</body>

小提示:

  • 通过 e.target.innerHTML 获取的是把换行符替代成 <div> 标签的 html 代码
  • 通过 e.target.innerText 获取的是把换行符替代成 ↵ 符号的文本

试验二

在试验一的基础上,将其和 Vue 一起使用

1. 显示框

<template>
<div class="content">{{info}}</div>
</template>

<style>
    .content {
        white-space: pre-wrap;
        word-wrap: break-word;
        /* word-break: break-all; */
    }
</style>
<template>
<div class="content">{{info}}</div>
</template>

<style>
    .content {
        white-space: pre-wrap;
        word-wrap: break-word;
        /* word-break: break-all; */
    }
</style>

2. 输入框

<template>
    <!-- 使用 contenteditable 来使 div 可编辑 -->
    <div class="input-wrap" contenteditable @input="selectAnswer"></div> 
</template>

<script>
export default {
    data () {
        return {
        }
    },
    methods : {
        selectAnswer (e) {
            var info =  e.target.innerText     //注意是 innerText
        }   
    }
}
</script>
<template>
    <!-- 使用 contenteditable 来使 div 可编辑 -->
    <div class="input-wrap" contenteditable @input="selectAnswer"></div> 
</template>

<script>
export default {
    data () {
        return {
        }
    },
    methods : {
        selectAnswer (e) {
            var info =  e.target.innerText     //注意是 innerText
        }   
    }
}
</script>

3. 缺点

  • 好像 IE浏览器不支持对 <div contenteditable > 添加监听的 input 事件
  • innerText 的兼容性,FireFox 不支持 innerText 属性,但是其支持 textContext 属性,所以需要写一个兼容的方法,需要注意的是两者虽然相似,但是还是有点区别,innerText 会忽略行内的样式和脚本,而 textContext 则会像返回其他文本一样返回行内样式和脚本代码。

最终方案

1. 显示框

<template>
<div class="content">{{info}}</div>
</template>

<style>
    .content {
        white-space: pre-wrap;
        word-wrap: break-word;
        /* word-break: break-all; */
    }
</style>
<template>
<div class="content">{{info}}</div>
</template>

<style>
    .content {
        white-space: pre-wrap;
        word-wrap: break-word;
        /* word-break: break-all; */
    }
</style>

2. 输入框

设置 textarea 的高度跟其父元素 div 的高度一致(height: 100%),当父元素 div 被内容撑大时,里面的 textarea 也会跟着变大, 并设置其相对于 div 进行绝对定位,覆盖住 div 。

复制 textarea 里面输入的内容到 div 里,将其高度撑高,由于需要对内容 ( 换行、回车符等 ) 的表现跟 textarea,所以需要设置 white-space: pre-wrap word-wrap: break-word

<template>
    <dir class="answer-wrap">{{questionAnswer}}
        <!-- 注意这里把 textarea 里面的内容复制进来了 -->
        <textarea class="issue-answer" @input="selectAnswer($event)"></textarea>
    </dir>
</template>

<script>
export default {
    data () {
        return {
            questionAnswer: ''
        }
    },
    methods : {
        selectAnswer (event) {
            var target = event.target ? event.target : event.srcElement;
            this.questionAnswer = target.value
            console.log(this.questionAnswer)
        }
    }
}
</script>

<style>
/* 统一div 和 textarea 的字体大小和 padding */
.answer-wrap, .issue-answer {
    padding: 6px;
    font-size: 16px;
}
    
.answer-wrap {
    position: relative;
        
    padding: 0 8px;
    margin: 10px 8px;
    min-height: 150px;

    white-space: pre-wrap;  /* 使该 div 的填充字体时表现出来的换行效果与 textarea 一致 */
    word-wrap: break-word;  /* 使该 div 的填充字体时表现出来的换行效果与 textarea 一致 */
    /* word-break: break-all; */
}
    
.issue-answer {
    position: absolute;
    top: 0px;
    right: 0;

    width: 100%;
    height: 100%;  /* 使 textarea 的高度跟其父元素的高度保持一致 */
        
    color: #495060;
    border: 1px solid #dedede;
    outline: none;
}
</style>
<template>
    <dir class="answer-wrap">{{questionAnswer}}
        <!-- 注意这里把 textarea 里面的内容复制进来了 -->
        <textarea class="issue-answer" @input="selectAnswer($event)"></textarea>
    </dir>
</template>

<script>
export default {
    data () {
        return {
            questionAnswer: ''
        }
    },
    methods : {
        selectAnswer (event) {
            var target = event.target ? event.target : event.srcElement;
            this.questionAnswer = target.value
            console.log(this.questionAnswer)
        }
    }
}
</script>

<style>
/* 统一div 和 textarea 的字体大小和 padding */
.answer-wrap, .issue-answer {
    padding: 6px;
    font-size: 16px;
}
    
.answer-wrap {
    position: relative;
        
    padding: 0 8px;
    margin: 10px 8px;
    min-height: 150px;

    white-space: pre-wrap;  /* 使该 div 的填充字体时表现出来的换行效果与 textarea 一致 */
    word-wrap: break-word;  /* 使该 div 的填充字体时表现出来的换行效果与 textarea 一致 */
    /* word-break: break-all; */
}
    
.issue-answer {
    position: absolute;
    top: 0px;
    right: 0;

    width: 100%;
    height: 100%;  /* 使 textarea 的高度跟其父元素的高度保持一致 */
        
    color: #495060;
    border: 1px solid #dedede;
    outline: none;
}
</style>

如果是使用原生 Js 写的话也是监听 textarea 的 input 事件然后在回调函数里写 div.innerHTML = textarea.value 就行