contenteditable=“true“属性的标签输入框,内容实现高度自适应以及其他处理

文章目录

        • 一. 功能需求
            • 1.1 输入框未获取焦点、失去焦点且输入框无内容的情况下,如下图:
            • 1.2 输入框获取焦点情况下,如下图:
            • 1.3 用户最多输入200个字符, 想要的效果如下图:(给出提示,只保留200个字符,且光标在文末)
            • 1.4 复制内容进输入框时,想要的效果如下图:
            • 1.5 输入三排字以后输入框高度固定,超过三排字的内容可以上下滑动看。若想要输入框高度一直自适应,则输入框的css不要加height和max-height。
        • 二. 代码实现
            • 2.1 实现上面1.1和1.2功能,代码如下(采坑版)
            • 2.2 实现上面1.1和1.2功能新方法:通过伪类元素,代码如下:
            • 2.3 实现上面1.3功能,限制输入文字长度,并且光标显示在文末
            • 2.4 实现上面2.4功能,在别处复制内容插入输入框时,处理掉复制的样式,只拿取文本部分
            • 2.5 移动端测试的问题

一. 功能需求

1.1 输入框未获取焦点、失去焦点且输入框无内容的情况下,如下图:

在这里插入图片描述

1.2 输入框获取焦点情况下,如下图:

在这里插入图片描述

1.3 用户最多输入200个字符, 想要的效果如下图:(给出提示,只保留200个字符,且光标在文末)

contenteditable=“true“属性的标签输入框,内容实现高度自适应以及其他处理_第1张图片
而不是这样的效果,如下图:(光标跑到了文首)

contenteditable=“true“属性的标签输入框,内容实现高度自适应以及其他处理_第2张图片

1.4 复制内容进输入框时,想要的效果如下图:

contenteditable=“true“属性的标签输入框,内容实现高度自适应以及其他处理_第3张图片
而不是这样的效果,如下图:
contenteditable=“true“属性的标签输入框,内容实现高度自适应以及其他处理_第4张图片

1.5 输入三排字以后输入框高度固定,超过三排字的内容可以上下滑动看。若想要输入框高度一直自适应,则输入框的css不要加height和max-height。

二. 代码实现

刚开始我选择的是textarea标签,但是该标签不能随着文本内容自动变化高度,需要通过js操作dom来动态改变,比较麻烦,我就放弃了。选择了HTML5的一个属性contenteditable。

思路:默认时,输入框是有一个高度的。我这里的需求是输入三排文字后输入框高度就固定,所以你在算好相关padding,margin以及文字的line-height以后,给与输入框一个max-height即可。

2.1 实现上面1.1和1.2功能,代码如下(采坑版)
<div id="textarea" ontenteditable="true"  @focus="removeDefaultContent" @blur="addDefaultContent">
   <img src="~images/index/[email protected]" alt="">
   <span>随便说说</span> 
</div>

思路:获取焦点的时候,通过js操作innerHTML把内容清空。失去焦点的时候判断innerHTML有没有值,没有的话,重新通过js操作innerHTML把span和img标签加上。

问题:通过js操作innerHTML把span和img标签加上,图片不显示,审查元素发现img标签的src地址是http://192.168.0.108:8089/images/index/[email protected],本来是本地的图片,现在反而是访问本地服务器了。

解决: img标签不写src属性,而是通过css样式添加背景图像。

结果:js操作innerHTML把span和img标签加上后,图片依然不显示,且我才发现img和span的css样式根本就没生效。

原因: vue中css编译完成后类名后会有随机码做唯一标识,这就导致了拼接的html中的类名与编译后的类名不同,也就无法生效了。

解决: 参考https://www.cnblogs.com/hhyf/p/11409386.html

我没有采用这种方法,想了下拼接的html不行的话,那我通过伪类元素直接给你自身添加,这样不管你的标识码怎么变,你始终还是那个你,给你加就没拐,哈哈哈。

2.2 实现上面1.1和1.2功能新方法:通过伪类元素,代码如下:

思路: 通过伪类元素直接给输入框加默认的图片和placeholder内容,输入文字的样式通过id标签加上,伪类元素的样式通过class属性加上。当输入框获取焦点时,移除class样式,当失去焦点时,若没有输入内容或者全为空的情况下,添加class样式。

结果: 可。

html部分:
<!-- 回答问题部分 -->
 <div class="to-answer-question">
    <!-- 输入框部分 -->
    <div ref="textarea" class="textarea" id="textarea" contenteditable="true" placeholder="随便说说" @focus="removeDefaultContent" @blur="addDefaultContent" @input="changeAnswerContent" @paste="optimizePasteEvent">
    </div>

    <!-- 发送输入内容 -->
    <div id="send-answer" class="send-answer" @click.stop="sendAnswer">
      <img src="~images/index/[email protected]" alt="">
      <span>发送</span>
    </div>

    <!-- 分享功能(暂时不实现) -->
    <div class="share-question" v-if="showShareQuestion"> 
      <img src="~images/index/[email protected]" alt="">
      <span>分享</span>
    </div>
</div>

css部分(scss):
.to-answer-question {
  width: 100%;
  background: #fff;
  padding: 8px 16px;
  position: fixed;
  left: 0;
  bottom: 0;
  z-index: 1000;
  box-shadow: 0 -1px 1px 0 rgba(100, 100, 100, .05);
  display: flex;
  justify-content: space-between;
  align-items: center;

  #textarea {
    width: 100%;
    height: auto;
    font-size: 13px;
    color: #333;
    line-height: 16px;
    text-align: left;
    margin-right: 20px;
    background: #f8f9ff;
    border-radius: 8px;
    padding: 6px 13px;
    max-height: 54px;
    word-wrap: break-word;
    overflow-x: hidden;
    overflow-y: auto;
    overflow-y: visible;
  }
  .textarea {
    &:before {
      content: '';
      display: inline-block;
      width: 16px;
      height: 16px;
      background-image: url('~images/index/[email protected]');
      background-size: 100%;
      vertical-align: middle;
      margin-right: 8px;
    }
    &:after {
      content: attr(placeholder);
      display: inline-block;
      color: #999;
      font-size: 13px;
      vertical-align: middle;
      line-height: 16px;
    }
  }

  .send-answer {
    flex-shrink: 0;
    display: flex;
    justify-content: flex-start;
    align-items: center;
    img {
      width: 16px;
      height: 16px;
      margin-right: 3px;
    }
    span {
      font-size: 15px;
      color: #ff586b;
      line-height: 16px;
    }
  }

  .share-question {
    flex-shrink: 0;
    margin-left: 12px;
    display: flex;
    justify-content: flex-start;
    align-items: center;
    img {
      width: 20px;
      height: 20px;
      margin-right: 2px;
    }
    span {
      font-size: 13px;
      color: #333;
      font-weight: bold;
      line-height: 18px;
    }
  }
}

js代码部分:
export default {
  data() {
    return {
      answerContent: sessionStorage.getItem('answerContent') ? sessionStorage.getItem('answerContent') : null, // 用户对于问题回答的内容
      showShareQuestion: true, // 是否展示分享图标
    }
  },
    
  methods: {
    // 输入框失去焦点时触发
    addDefaultContent(event) {
      // 拿到当前内容
      let textareaContent = event.target.innerText.trim()

      // 没有输入内容或者输入了很多空格后失去焦点,则显示默认内容
      if (textareaContent.length == 0) {
	     // 若全部输入的空格,则先清空
         this.$refs.textarea.innerText = ''
         // 给于伪类元素的css样式名
         this.$refs.textarea.setAttribute('class','textarea')
        
         // 展示分享按钮
         this.showShareQuestion = true
       } else {
         // 仅用于用户在未登录时,在输入框中输入了内容的情况(登录后重新回到该界面时保留之前输入的内容)
         if (!sessionStorage.getItem('userInfo')) {
           sessionStorage.setItem('answerContent', this.answerContent)
         }
       } 
    },

    // 输入框获取焦点时触发,去除默认placeholder且隐藏分享按钮
    removeDefaultContent(event) {
      if (event.target.innerText.length == 0) {
        this.$refs.textarea.classList.remove("textarea")

        this.showShareQuestion = false
      }
    },
  }  
}
2.3 实现上面1.3功能,限制输入文字长度,并且光标显示在文末

过程: 在测试过程中,我发现每次截取前200个值重新赋值给输入框后,光标跑到了文首,这样就很不方便操作了,所以在重新赋值后需要处理光标,将光标放在文末。

结果: 可。

参考: https://blog.csdn.net/qq_31061615/article/details/80263746以及https://developer.mozilla.org/zh-CN/docs/Web/API/Selection

html输入框部分:
<div ref="textarea" class="textarea" id="textarea" contenteditable="true" placeholder="随便说说" @focus="removeDefaultContent" @blur="addDefaultContent" @input="changeAnswerContent" @paste="optimizePasteEvent">
</div>

js代码部分:
methods: {
  // 监听输入框内容的实时变化,然后及时赋值给对应的变量
  changeAnswerContent(event) {
    let textareaContent = event.target.innerText.trim()
    
    if (textareaContent.length > 200) {
       this.$toast('最多允许输入200个字符哦')
       textareaContent = textareaContent.slice(0, 201)
       // 截取前200个赋值给当前文本框
       this.$refs.textarea.innerText = textareaContent

       // 将光标重新定位到内容最后
       this.$nextTick(() => {
         this.keepCursorEnd(event.target)
       })
     }

     // 拿到用户输入的值
     this.answerContent = textareaContent
   },
    
   // 将光标重新定位到内容最后
   keepCursorEnd(obj) {
      // ie11 10 9 firefox safari
      if (window.getSelection) {
      	// 解决firefox不获取焦点无法定位问题
        obj.focus()
        
        // 创建range
        let range = window.getSelection()
        
        // range 选择obj下所有子内容
        range.selectAllChildren(obj)
        
        // 光标移至最后
        range.collapseToEnd()
      } else if (document.selection) { //ie10 9 8 7 6 5
        // 创建选择对象
        let range = document.selection.createRange()
        
        //range定位到obj
        range.moveToElementText(obj)
        
        //光标移至最后
        range.collapse(false)
        range.select()
      }
    },
}
2.4 实现上面2.4功能,在别处复制内容插入输入框时,处理掉复制的样式,只拿取文本部分

过程: 在测试的过程中,发现2.3中光标的定位处理,会只拿当前内容的文本然后将光标定位到最后,这样其实也处理了复制内容插入的样式问题。我测了很多次都没问题,为了以防万一以及学习新知识,我还是加上了@paste="optimizePasteEvent"事件的监听来进行处理。

结果: 可。

参考: https://segmentfault.com/q/1010000009988202及https://developer.mozilla.org/zh-CN/docs/Web/API/ClipboardEvent/clipboardData以及https://developer.mozilla.org/zh-CN/docs/Web/API/Document/execCommand

html输入框部分:
<div ref="textarea" class="textarea" id="textarea" contenteditable="true" placeholder="随便说说" @focus="removeDefaultContent" @blur="addDefaultContent" @input="changeAnswerContent" @paste="optimizePasteEvent">
</div>

js代码部分:
method: {
  // 监听粘贴内容到输入框事件,对内容进行处理
  optimizePasteEvent(e) {
    e.stopPropagation()
    e.preventDefault()
    let text = '', event = (e.originalEvent || e)
    if (event.clipboardData && event.clipboardData.getData) {
       text = event.clipboardData.getData('text/plain')
    } else if (window.clipboardData && window.clipboardData.getData) {
       text = window.clipboardData.getData('text')
    }

    if (document.queryCommandSupported('insertText')) {
      document.execCommand('insertText', false, text)
    } else {
      document.execCommand('paste', false, text)
    }
  },
}
2.5 移动端测试的问题

问题: 在移动端测试的时候发现一个问题,输入框一旦获取焦点以后,点击其他任何地方(提交以及分享按钮除外)焦点会一直保留,这样就很恼火了。我收下输入框,由于焦点还存在,不管我点或者滑动其他地方时,输入框会马上弹上来。

解决: 监听当前页面的滑动事件,如果事件对象不是输入框时,则让其失焦。

注意: 离开该页面时,一定要移除事件监听,不然在某些地方点击或者触摸时可能会报blur of undefined错误。

结果: 可。

 mounted() {
   this.$nextTick(() => {
     // 进入页面前看用户是否在登录前已经输入了文字,如果是的话需要展示之前的文字
     if (sessionStorage.getItem('answerContent') != null) {
       // 若有,则需要移除伪类默认样式
       this.$refs.textarea.classList.remove("textarea")
       // 展示用户之前输入内容
       this.$refs.textarea.innerText = sessionStorage.getItem('answerContent')

       // 隐藏分享按钮
       this.showShareQuestion = false
     }
   })

    // 监听点击事件对象是不是输入框,不是的话取消输入框焦点
    window.addEventListener("touchstart", this.selectConfirmBlur)
  },

  // 离开组件前需要移除监听事件,不然会引起blur of undefined的报错
  beforeDestroy() {
    window.removeEventListener('touchstart', this.selectConfirmBlur);
  },
  
  methods: {
  	// 当触摸或点击位置不是输入框时,取消输入框焦点
    selectConfirmBlur(event) {
      if (event.target.id != 'textarea') {
        this.$nextTick(() => {
          this.$refs.textarea.blur()
        })

        // 如果是点击的发送,那么不仅取消焦点,还应该触发发送按钮
        if (event.target.parentNode.id == 'send-answer') {
          this.sendAnswer()
        }
      }
    },
  }

完。

你可能感兴趣的:(JavaScript,HTML5,CSS,vue)