美文网首页让前端飞Web前端之路
手机端聊天框实现图片表情连续插入

手机端聊天框实现图片表情连续插入

作者: 不二很纯洁 | 来源:发表于2020-11-23 16:18 被阅读0次

    效果

    直接显示表情,且可以在文字中间插入表情,包括换行显示。


    实现效果

    难点

    要想实现文字图片混排,需要用到富文本编辑技术(即添加contenteditable="true"属性),但是使用document.execCommand命令会让文本框会获得焦点,使表情选择框隐藏,如果在插入之后使文本框失去焦点,在苹果手机会因为软键盘弹出收起而导致页面跳动,为了解决这个问题,只能抛弃execCommand命令,把图片直接插入到指定位置。
    ps:富文本编辑器会单独写一篇记录,聊天框使用的只是一个简易版富文本编辑器

    需要用到的API

    实现

    本示例使用的vue,如是其他请自行转换,关键代码都是原生js实现。

    • html
    <div>
        <div class="editorBox">
            <!-- 输入框 -->
            <div
                class="editor" 
                ref="chatTextarea"
                id="chatTextarea"
                contenteditable="true" 
                @paste="editorPaste"
                @blur="editorBlur" 
                @focus="editorFocus" 
                @input="editorInput">
            </div>
            <!-- 表情开关 -->
            <div class="icon" @click="openEmoticon">表情</div>
            <!-- 发送按钮 -->
            <div class="send" @click="submitSend">发送</div>
        </div>
        
        <!-- 表情区域 -->
        <div class="emoticonBox" v-show="emoticonShow">
            <img 
                class="emoji"
                v-for="(v,k) in emojiData"
                :src="'static/emoticon/emoji/'+v+'.png'"
                @click="chooseEmoji(k)"
                :key="k" />
        </div>
    </div>
    
    
    
    • js
    // ...
    export default {
        data() {
            return {
                // ...
                emoticonShow: false,
                emojiData: {},  // 自己的表情数据
                currentSelection: {
                    startContainer: null,
                    startOffset: 0,
                    endContainer: null,
                    endOffset: 0
                },
            }
        },
        mounted() {
            // ...
            this.init();
        },
        methods: {
            // 初始化编辑器
            init(){
                const editor = this.$refs.chatTextarea;
                // 光标位置为开头
                this.currentSelection = {
                    startContainer: editor,
                    startOffset: 0,
                    endContainer: editor,
                    endOffset: 0
                }
                // 设置光标位置
                this.restorerange();
            },
            // 备份当前光标位置
            backuprange() {
                let selection = window.getSelection();
                if (selection.rangeCount > 0) {
                    let range = selection.getRangeAt(0);
                    this.currentSelection = {
                        startContainer: range.startContainer,
                        startOffset: range.startOffset,
                        endContainer: range.endContainer,
                        endOffset: range.endOffset
                    };
                }
            },
            // 设置光标位置
            restorerange() {
                if(this.currentSelection){
                    let selection = window.getSelection();
                    selection.removeAllRanges();
                    let range = document.createRange();
                    range.setStart(this.currentSelection.startContainer, this.currentSelection.startOffset);
                    range.setEnd(this.currentSelection.endContainer, this.currentSelection.endOffset);
                    // 向选区中添加一个区域
                    selection.addRange(range);
                }
            },
            // 插入text文本
            inserText(text) {
                // 插入前先恢复上次的光标位置
                this.restorerange();
                document.execCommand('insertText', false, text);
            },
            // 插入html片段
            insertHTML(html) {
                // 插入前先恢复上次的光标位置
                this.restorerange();
                document.execCommand('insertHTML', false, html);
            },
    
            // 粘贴事件
            editorPaste(e){
                // 阻止默认事件
                e.preventDefault();
                const clp = e.clipboardData || e.originalEvent && e.originalEvent.clipboardData;
                // 获取纯文本与富文本
                let plainText,htmlText;
                if (clp == null) {
                    plainText = window.clipboardData && window.clipboardData.getData('text');
                } else {
                    plainText = clp.getData('text/plain');
                    htmlText = clp.getData('text/html');
                }
                // 某些ios手机获取不到富文本,需要处理一下
                if (!htmlText && plainText) {
                    htmlText = '<div>' + plainText.replace(/[\n\r]|[\r\n]|[\r]|[\n]/g,'</div><div>') + '</div>';
                }
                if (!htmlText) {
                    return;
                }
                // 聊天框只使用纯文本,富文本编辑器会用到html
                this.inserText(plainText);
    
                // 备份当前光标
                this.backuprange();
            },
            // 失去焦点
            editorBlur(){
                // 备份当前光标位置
                this.backuprange();
            },
            // 获取焦点
            editorFocus(){
                // 处理获取到焦点的逻辑
                // ...
                // 隐藏表情区域
                this.emoticonShow = false;
            },
            // 输入中
            editorInput(){
                // 备份当前光标位置
                this.backuprange();
            },
    
            // 打开表情
            openEmoticon(){
                this.emoticonShow = true;
            },
    
            // 选择插入表情/图片
            chooseEmoji(key){
                const node = new Image();
                node.src = '你需要插入的图片地址';
                if(this.currentSelection.startContainer.nodeType==3){
                    // 如果是文本节点,拆分文字
                    const newNode = this.currentSelection.startContainer.splitText(this.currentSelection.startOffset);
                    // 设置光标开始节点为拆分之后节点的父级节点
                    this.currentSelection.startContainer = newNode.parentNode;
                    // 在拆分后得到的节点之前插入图片
                    this.currentSelection.startContainer.insertBefore(node, newNode);
                }else{
                    // 非文本节点
                    if(this.currentSelection.startContainer.childNodes.length){
                        // 如果光标开始节点下有子级,获取到光标位置的节点
                        const beforeNode = this.currentSelection.startContainer.childNodes[this.currentSelection.startOffset];
                        // 插入
                        this.currentSelection.startContainer.insertBefore(node, beforeNode);
                    }else{
                        // 如果光标开始节点下没有子级,直接插入
                        this.currentSelection.startContainer.appendChild(node);
                    }
                }
    
                // 获取插入的节点所在父级下的位置
                const index = Array.from(this.currentSelection.startContainer.childNodes).indexOf(node);
                this.currentSelection.startOffset = index+1;
                this.currentSelection.endOffset = index+1;
                
                // 视图滚动带完全显示出来
                node.scrollIntoView(false)
            },
    
            // 发送
            submitSend(){
                const html = this.$refs.chatTextarea.innerHTML;
                // 发送逻辑
            },
        }
    }
    
    • css

    按照设计稿自行添加

    最后

    主要逻辑都有注释,有更好的实现方式或有问题欢迎留言,我会持续更新~

    相关文章

      网友评论

        本文标题:手机端聊天框实现图片表情连续插入

        本文链接:https://www.haomeiwen.com/subject/bmhviktx.html