IM即时消息_发言框实战总结

实战主要是使用框架和灵活组合原生Api为业务服务,最终达到产品需求,满足UI设计;提供客户方便;本文简明扼要讲解vue3和react18怎么使用和实现;

输入框绑定
// less
input_box{
	height: calc(100% - 58px);
	box-sizing: border-box;
	// 可选
	outline: none;
	// 换行
	white-space: pre-line;
	word-break: break-all;
	word-break: break-word;
	// 滚动条
	overflow-y: auto;
	// 如果表情是图片的情况下
	img{
	    width: 20px;
	    height: 20px;
	    vertical-align: bottom;
	}
}
/* react18 绑定事件:
	onblur: 失去焦点后记录光标的位置
	onPaste: 监听粘贴事件,比方说微信截图或复制了文字
	onKeyDown: 监听键盘按下,比方说监听Enter(回车键)提交,Ctrl+Enter换行
*/
import {useRef } from 'react';
export default function SpeakComponent () {
	const speakRef: any = useRef(null);
	return (
		<div className={`${Style.input_box}`}
			contentEditable="true"
			id="speakId"
			ref={speakRef}
			onBlur={methods.handleDivOnBlur}
			onPaste={methods.handleDivOnPaste}
			onKeyDown={(event) => methods.handleSpeakOnkeyDown(event)}>	
		</div>
	)
}

/* vue3 绑定事件:
	@blur: 失去焦点后记录光标的位置
	@Paste: 监听粘贴事件,比方说微信截图或复制了文字
	@KeyDown: 监听键盘按下,比方说监听Enter(回车键)提交,Ctrl+Enter换行
*/
<template>
	<div class="input_box"
		contentEditable="true" 
		id="speakId" 
		ref="speakRef"  
		@blur="handleDivOnBlur()"
		@paste="handleDivOnPaste($event)" 
		@keydown="handleSpeakOnkeyDown($event)">  
	</div>
</template>
<script lang="ts">
import { ref } from 'vue';  
const speakRef = ref(null);  
export default {  
  setup() {
	const speakRef = ref(null);  
	const handleDivOnBlur = () => { /* ... */ };  
	const handleDivOnPaste = () => { /* ... */ };  
	const handleSpeakOnkeyDown = (event) => { /* ... */ };  
	  
	return {  
	  speakRef,  
	  handleDivOnBlur,  
	  handleDivOnPaste,  
	  handleSpeakOnkeyDown,  
	};  
  }
}
</script>
监听按键onkeydown
/*  @keydown="handleSpeakOnkeyDown($event)">    */
handleSpeakOnkeyDown(event: any) { // 按键监听
    //  如果按下Ctrl+Enter就插入换行
	if(event.ctrlKey && event.keyCode === 13){
	    methods.handleSpeakLineFeed();
	    return false;
	}else if (event.keyCode === 13) { // 如果按下Enter就提交
		// 阻止浏览器默认换行操作
	    event.preventDefault(); 
	    methods.handleSubmit();
	    return false;
	}
}
// 插入换行方法
handleSpeakLineFeed() {
	const Br = `<br/><br/>`;
	/* 把Br插入到输入框中 */
	/* 请参考页面中: 插入标签到输入框方法*/
	methods.handleInsertContent(Br);
};
// 提交
handleSubmit() {
	/* Vue3
		const speakEl: any = speakRef.value; 
	*/
	/*  React
		const speakEl: any = speakRef.current; 
	*/
	const speakEl = document.getElementById('speakId');
	const str = speakEl.innerHTML;
	result = result.replace(/<br>/g, '[换行]');
    result = result.replace(/\[换行\]/g, function(hh: string) {
        return '\n';
    });
    result = result.replace(/&nbsp;/g, function(hh: string) {
        return '';
    });
    const isEmpty = result.replace(/\s/g, '');
    if (isEmpty === '') {
        console.log('waring', '消息不能为空');
        return;
    };
}
插入标签到输入框方法
handleInsertContent(Element: string) {
	// 获取光标对象
	const selection: any = getSelection();
	if (!selection) return;
	// 获取当前光标位置
	const range = selection.getRangeAt(0);
	// 创建Dom
	const node = range.createContextualFragment(Element);
	// 获取当前元素最后一个子元素
	const c = node.lastChild;
	// 插入创建Dom
	range.insertNode(node);
	// 插入成功后光标移动到插入Dom后面
	if (c) {
	    range.setEndAfter(c);
	    range.setStartAfter(c);
	};
}
监听方法onblur
// 失去焦点后记录最后一次光标的位置,后面插入表情的时候用的到
handleDivOnBlur() {
	const selection = getSelection();
    if (!selection) return;
    /*
    	React 写法
    	const lastEditRange = useRef<any>(null);
    	使用 lastEditRange.current = selection?.getRangeAt(0);
    	Vue3 写法
    	const lastEditRange = ref(null);
    	使用 lastEditRange.value = selection?.getRangeAt(0);
	*/
    lastEditRange.current = selection?.getRangeAt(0);
}
// 插入表情
handleEmojiClick(content:string) {
	// 获取光标对象
	const selection: any = getSelection();
	if (!selection) return;
	selection.removeAllRanges();
	/*
		React 写法
			selection.addRange(lastEditRange.current);
		vue3 写法
			selection.addRange(lastEditRange.value);
	*/
    selection.addRange(lastEditRange.current);
    var emojiText: any = content;
    /*
    	如果你的表情是图片,不是emoji字体
 		const Img = `<img class="text-emoji" name="${item.text}" src="${item.url}"/>`
 		哪里获取emoji字体:https://emojixd.com/group/smileys-emotion#h-face-smiling
   	*/
   	/* 请参考页面中: 插入标签到输入框方法*/
    methods.handleInsertContent(emojiText);
}
监听方法onPaste
handleDivOnPaste() {
	e.stopPropagation();
	e.preventDefault();
	const selection = getSelection();
	if (!selection) return;
	lastEditRange.current = selection.getRangeAt(0);
	if (!(e.clipboardData && e.clipboardData.items) ) {
	   return ;
	};
	const items = e.clipboardData.items;
	if (items.length) {
	   let item = items[0];
	   if (item.kind === "string") {
	       item.getAsString(function (str: string) {
	           if (!str) return;
	           if (!lastEditRange.current) return;
	           // 处理文本复制
	           methods.handleTextCopy(str);
	       });
	   }
	   if (item.kind === "file") {
	       var pasteFile = item.getAsFile();
	       if (pasteFile.type === "image/png") {
       			/* 调用自己的 axios/AJAX 上传图片 */
	       };
	   };
	 }
}
处理文本复制方法
/* 处理复制文本 */
handleTextCopy(str: string) { // 处理文字复制
	/*输入框对象
		vue3 
		const speakRefVal : any = speakRef.value; 
		react18
		var speakRefVal = speakRef.current;
	*/	
	var speakRefVal = document.getElementById('speakId');
	var selection: any = getSelection();
	// 判断选定对象范围是编辑框还是文本节点
	if (selection.anchorNode.nodeName != '#text') {
	    // 如果是编辑框范围。则创建文本节点进行插入
	    var emojiText = document.createTextNode(str)
	    if (speakRefVal.childNodes.length > 0) {
	        // 如果文本框的子元素大于0,则表示有其他元素,则按照位置插入节点
	        for (var i = 0; i < speakRefVal.childNodes.length; i++) {
	            if (i == selection.anchorOffset) {
	                speakRefVal.insertBefore(emojiText, speakRefVal.childNodes[i])
	            };
	        };
	    } else {
	        // 否则直接插入一个表情元素
	        speakRefVal.appendChild(emojiText)
	    }
	    // 创建新的光标对象
	    var range: any = document.createRange()
	    // 光标对象的范围界定为新建的表情节点
	    range.selectNodeContents(emojiText)
	    // 光标位置定位在表情节点的最大长度
	    range.setStart(emojiText, emojiText.length)
	    // 使光标开始和光标结束重叠
	    range.collapse(true)
	    // 清除选定对象的所有光标对象
	    selection.removeAllRanges()
	    // 插入新的光标对象
	    selection.addRange(range)
	} else {
	    // 如果是文本节点则先获取光标对象
	    var range: any = selection.getRangeAt(0);
	    // 获取光标对象的范围界定对象,一般就是textNode对象
	    var textNode: any = range.startContainer;
	    // 获取光标位置
	    var rangeStartOffset = range.startOffset;
	    // 文本节点在光标位置处插入新的表情内容
	    textNode.insertData(rangeStartOffset, str);
	    // 光标移动到到原来的位置加上新内容的长度
	    range.setStart(textNode, rangeStartOffset + str.length);
	    // 光标开始和光标结束重叠
	    range.collapse(true);
	    // 清除选定对象的所有光标对象
	    selection.removeAllRanges();
	    // 插入新的光标对象
	    selection.addRange(range);
	}
	lastEditRange.current = selection.getRangeAt(0);
 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值