高度自適應的輸入框


有時候我們需要一個高度能隨內容自動增加的輸入框,input 顯然不行,因為 input 里的文字是不換行的。文本域 textarea 里的文字倒是換行的,可一旦文字內容超過其高度,textarea 就會增加一個煩人的滾動條,這是很影響視覺的,就如同下面:

<textarea cols="30" rows="3"></textarea>

  那么有沒有辦法制作一個高度能隨文字內容自動增加的輸入框呢?答案是肯定的,下面介紹兩種方式。

方式一

  這種方式依然使用 textarea, 主要思想是我們將 textarea 放入一個容器中,同時在這個容器中放入一個隱藏的 div (visibility: hidden), 監聽 textarea 的輸入事件並將其中的文字動態的同步到隱藏的div中,這樣div 就可以撐開容器,這時設置 textarea 的高度為 100% 並將其定位到容器的左上角,那么 textarea 的高度自然就是其中文字內容的高度了。

visibility 是一個CSS屬性,用來在不更改文檔的布局的前提下顯示或隱藏元素,它有三個可能的取值:

  1. visible 元素正常顯示(默認值);
  2. hidden 隱藏元素,但是其他元素的布局不改變,相當於此元素變成透明。
      若將其子元素設為 visibility: visible,則該子元素依然可見;
  3. collapse 用於表格的行、列、列組和行組,隱藏表格的行或列,並且不占用任何空間。
   <!-- demo-1.html -->

   <div class="container">
      <div></div>
      <textarea placeholder="輸入消息..."></textarea> 
   </div>
   
   <style>
      .container {
         width: 500px;
         position: relative;
      }
      .container div {         
         visibility: hidden;
         /** 避免初始化時容器沒有高度 */
         padding: 8px 0px;
      }
      .container textarea {
         width: 100%;
         height: 100%;
         position: absolute;
         top: 0px;
         padding: 0px;
         /** 必須設置為 content-box !!! */ 
         box-sizing: content-box;
      }
   </style>

   <script>
      const textarea = document.querySelector('.container textarea');
      const div = document.querySelector('.container div');
      textarea.addEventListener('input', (e) => {
         div.innerText = e.target.value;
      });
   </script>

查看樣例:https://mxsyx.site/archives/10/#demo-1

  你可能已經注意到了,當我們輸入文字時,輸入框的高度顯然要比文字內容高許多,伴隨着輸入文字的增多。高度差會越來越大,這是因為隱藏 div 與 文本域 textarea 內字體的尺寸與行高是不同的, div 內的字體尺寸與行高要比 textarea 內的大,所以 div 撐開的容器高度自然要高於 textarea 內的文字內容高度。要解決這個問題,統一它們的字體尺寸與行高就可以了。(注:div 的字體尺寸與行高默認繼承自父元素)

   <!-- demo-1.html -->

   <div class="container">
      <div></div>
      <textarea placeholder="輸入消息..."></textarea> 
   </div>

   <style>
      .container {
         width: 500px;
         position: relative;
         font-size: 14px;
         line-height: 16px;
      }
      .container div {
         visibility: hidden;
         /** 避免初始化時容器沒有高度 */
         padding: 8px 0px;
      }
      .container textarea {
         width: 100%;
         height: 100%;
         position: absolute;
         top: 0px;
         padding: 0px;
         /** 必須設置為 content-box !!! */
         box-sizing: content-box;
         /** 設置字體尺寸與行高繼承自父元素 */
         font-size: inherit;
         line-height: inherit;
         /* 去掉右下角的調整大小的標志 */
         resize: none;
      }
   </style>
   <script>
      const textarea = document.querySelector('.container textarea');
      const div = document.querySelector('.container div');
      textarea.addEventListener('input', (e) => {
         div.innerText = e.target.value;
      });
   </script>

查看樣例:https://mxsyx.site/archives/10/#demo-2

  這樣一來高度就一致了。這種方式雖然可以較好的實現高度自適應的輸入框,但實現起來總感覺很粗糙,下面這種方式就明顯簡單多了。

方式二

  像 div, p, blockquote 這樣的元素默認是不可編輯的,但我們可以將其 contenteditable 屬性設置為 true, 使其變為可編輯的。

contenteditable是一個全局屬性,用於指示元素是否可被用戶編輯,該屬性必須采用以下值之一:

  1. true 或者 '空字符串', 表示該元素是可編輯的;
  2. false, 表示該元素是不可編輯的。
  3. 如果未設置此屬性,則其默認值將從其父元素繼承。
  <div class="container" contenteditable="true"></div>

  <style>
    .container {
      width: 500px;
      font-size: 14px;
      line-height: 16px;
      border: solid 1px #999;
    }
  </style>

嘗試輸入一段文字吧:

  是不是很簡單呢? 我們也可以使用CSS偽類 :empty, :focus, 實現placeholder 那樣的效果

<style>
.container {
  width: 500px;
  font-size: 14px;
  line-height: 16px;
  border: solid 1px #999;
}
.container:empty::before {
  content: "輸入消息...";
  color: #999999;
}
.container:focus::before {
  content: none;
}
</style>

嘗試輸入一段文字吧:

  如果你使用 Vue.js, 我們也可以它封裝為一個Vue組件:

<template>
    <div
      class="msg-input"
      contenteditable="true"
      @input="changeText"
    >{{ innerText }}</div>
</template>

<script>
export default{
  name: "MsgInput",
  props: ['value'],
  
  data: function() {
    return {
      innerText: this.value,
    }
  },
  
  methods: {
    changeText() {
      this.$emit('input', this.$el.innerText);
    }
  }
}
</script>

<style scoped>
.msg-input {
  width: 500px;
  font-size: 14px;
  line-height: 16px;
  border: solid 1px #999;
}
.msg-input:empty::before {
  content: "輸入消息...";
  color: #999999;
}
.msg-input:focus::before {
  content: none;
}
</style>

接下來在父組件中引用這個組件:

<template>
  <div>
    <MsgInput v-model="msg"/>
  </div>
</template>

<script>
import MsgInput from "/MsgInput.vue";

export default {
  name: "MsgToolkit",
  data: function() {
    return {
      msg: ''
    }
  },

  components: {
    MsgInput
  },
}
</script>

  父組件為子組件使用 v-model 指令,將子組件的 value 與 父組件的 msg 雙向綁定在一起。當輸入事件發生后,子組件調用changeText方法,觸發一個 input 事件,父組件監聽到此事件,將事件傳遞過來的數據同步到 msg 上,由於數據是雙向綁定的,子組件的 value 值也會相應發生變化。更過原理請參考 自定義組件的-v-model

該篇博客內的代碼已同步到Github

參考資料:
[1]. MDN文檔 https://developer.mozilla.org/zh-CN/docs/Web/HTML/Global_attributes/contenteditable
[2]. MDN文檔 https://developer.mozilla.org/zh-CN/docs/Web/CSS/visibility
[3]. MDN文檔 https://developer.mozilla.org/zh-CN/docs/Web/CSS/:empty
[4]. MDN文檔 https://developer.mozilla.org/zh-CN/docs/Web/CSS/:focus
[5]. Vue.js官方文檔 https://cn.vuejs.org/v2/guide/components-custom-events.html#自定義組件的-v-model


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM