聊天框 contenteditable 上传图片及贴图

需求

  • Enter发送,Shift + Enter 换行
  • 上传图片–插入到输入框
  • 粘贴文本及图片

实现代码

chat.vue

<template>
	<div>
		<!-- 上传图片:选择图片插入到输入框 -->
		<div class="tooltip">
			<el-tooltip effect="dark" content="只能上传小于2M的图片" placement="top">
				<el-upload action="" accept=".jpg, .jpeg, .png" :show-file-list="false" :auto-upload="false" :on-change="getFile">
					<i class="sd-image"
				</el-upload>
			</el-tooltip>
		</div>
		<!-- 输入框 -->
		<div class="content-padding">
			<div id="editChat" ref="editChatRef" class="shortMessage-div__input scroll" contenteditable="true"
			@paste.prevent="handlePaste" @click="handleSelection" @input="handleSelection" @keydown="keydownEvent" />
		</div>
	</div>
</template>

<script>
	import onPaste from './paste'
	export default {
		data () {
			return {
				currentChildNodes: [], //用来存储输入框中子元素
				currentSel: null, //用来存储输入框当前的光标对象,用于上传图片
				range: null ,//用来存储输入框当前的光标位置,用于上传图片,
				limitNum: 10 //消息下行限制数
			}
		},
		mounted () {
			if (this.$refs.editChatRef) {
				// 默认聚焦,显示光标
				this.$refs.editChatRef.focus()
			}
		},
		methods: {
			async handlePaste (e) {
				const result = await onPaste(e)
				if (result.data.size > 2 * 1024 * 1024) {
					this.$message.error('图片大小超过2M')
					return
				}
				if (result.type === 'string') {
					// 粘贴文本
					document.execCommand('insertText', false, result.data)
				} else {
					// 粘贴图片
					const imgRegx = /^data:image/png|jpg|jpeg|gif|svg|bmp|tif/; // 支持的图片格式
				  if (imgRegx.test(result.compressedDataUrl)) {
					// document.execCommand('insertImage', false, result.compressedDataUrl);
					const sel = window.getSelection(); // 获取当前光标位置
					if (sel && sel.rangeCount === 1 && sel.isCollapsed) {
					  const range = sel.getRangeAt(0);
					  const img = new Image();
					  img.style= 'height: 90px'
					  img.src = result.compressedDataUrl; // 使用压缩后的图片
					  range.insertNode(img);
					  range.collapse(false);
					  sel.removeAllRanges();
					  sel.addRange(range);
					}
				  }
				}
			},
			handleSelection (e) {
				// 获取输入框中所有的子元素
				if (e.target && e.target.childNodes) {
					this.currentChildNodes = e.target.childNodes
				}
				// 存储输入框最后输入时的光标目标及位置,用于上传图片时,图片插入光标所在位置
				this.currentSel = window.getSelection()
				this.range = this.currentSel.getRangeAt(0)
			},
			keydownEvent (e) {
				if (!e.shiftKey && e.keyCode === 13) {
					e.cancelBubble = true
					e.stopPropagation()
					e.preventDefault()
					this.sendMessage()
					return false
				}
			},
			sendMessage () {
				if (!this.currentChildNodes.length) {
					this.$message.error('消息不能为空')
					return
				}
				let sendMessage = []
				// 实现根据输入框内容,汇总消息
				let joinFlag = false
				this.currentChildNodes.forEach(item => {
					if (item.nodeName === '#text' && item.data) {
						if (joinFlag) {
							sendMessage[sendMessage.length - 1]['text'] += '\n' + item.data
						}else {
							joinFlag = true
							sendMessage.push({
								type: 'text',
								text: item.data
							})
						}
					} else if (item.localName === 'img') {
						joinFlag = false
						sendMessage.push({
							type: 'image',
							text: item.src
						})
					}
				})
				// 是否超出消息下行限制数
				if (sendMessage.length > 0 && sendMessage.length < this.limitNum) {
					sendMessage.forEach(item => {
					// 发送消息
						this.doSend(item.text, item.type)
					})
					this.$nextTick(() => {
						// 重置剩余可发送消息数;输入框内容
						this.limitNum -= sendMessage.length
						this.currentChildNodes = []
						document.getElementById('editChat').innerHTML = ''
					})
				} else {
					this.$message.error('消息超过下行限制数')
				}
			}
		}
	}
</script>

<style scope lang="scss">
	.content-padding {
		display: flex;
		width: 100%;
		height: 80%;
		.shortMessage-div__input {
			width: 100%
		}
		.scroll {
			overflow: auto;
			overflow-y: -moz-overlay;
			overflow-y: -webkit-overlay;
			overflow-y: overlay;
			&::-webkit-scrollbar {
				width: 6px;
				height: 6px;
			}
			&:hover {
				&::-webkit-scrollbar-thumb {
					background-color: rgba(0,0,0,0.15);
					border-radius: 6px;
				}
				&::-webkit-scrollbar-thumb:hover {
					background-color: rgba(0,0,0,0.4);
				}
			}
			&::-webkit-scrollbar-thumb {
				background-color: rgba(0,0,0,0);
				border-radius: 6px;
			}
			&::-webkit-scrollbar-track-piece {
				background-color: transparent;
			}
			&::-webkit-scrollbar-thumb:hover {
				background-color: rgba(0,0,0,0.4);
			}
			&::-webkit-scrollbar-thumb {
				height: 5px;
				width: 5px;
			}
			&::-webkit-scrollbar-thumb:hover {
				background-color: rgba(0,0,0,0.3);
				border-radius: 6px;
			}
		}
	}
</style>

paste.js

实现获取粘贴的文本及图片

// 定义粘贴函数
const onPaste = (event) => {
  // 剪贴板没数据,则直接返回
	if (!event.clipboardData || !event.clipboardData.items) {
	  return;
	}
  return new Promise((resovle, reject) => {
    for(let i = 0, len = event.clipboardData.items.length; i < len; i++) {
      const item = event.clipboardData.items[i];
      if (item.kind === 'file') {
        const file = item.getAsFile();
        if (item.type.match('^image/')) {
          // 处理图片
          handleImage(file, (data) => {
            resovle(data)
          })
        } else {
          // 其他文件直接返回
          resovle({
            data: file,
            type: 'file'
          })
        }
      } else if () {
		  let str = event.clipboardData.getData('text')
		  resovle({
			  data: str,
			  type: 'string'
		  })
	  } else {
        reject(new Error('不支持粘贴该类型'));
      }
    }
  })
}
function handleImage(file, callback, maxWidth = 200) {
  console.log('粘贴的图片', file);
  if (!file || !/(?:png|jpg|jpeg|gif)/i.test(file.type)) {
    console.log('图片格式不支持');
    return;
  }
  const reader = new FileReader();
  reader.onload = function () {
    const result = this.result;
    console.log('compressedDataUrl', result);
    let img = new Image();
    img.onload = function() {
      let compressedDataUrl = compress(img, file.type, maxWidth, true);
      let url = compress(img, file.type, maxWidth, false);
      img = null;
      callback({
        data: file,
        compressedDataUrl,
        url,
        type: 'image'
      })
    }
    img.src = result;
  };
  reader.readAsDataURL(file);
}
function compress(img, type, maxHeight, flag) {
  let canvas = document.createElement('canvas');
  let ctx2 = canvas.getContext('2d');let ratio = img.width / img.height;
  let width = img.width, height = img.height;
  // 根据flag判断是否压缩图片
  if (flag) {
    // 压缩后的图片展示在输入框
    height  = maxHeight;
    width = maxHeight * ratio; // 维持图片宽高比
  }
  canvas.width = width;
  canvas.height = height;
​
  ctx2.fillStyle = '#fff';
  ctx2.fillRect(0, 0, canvas.width, canvas.height);
  ctx2.drawImage(img, 0, 0, width, height);let base64Data = canvas.toDataURL(type, 0.75);if (type === 'image/gif') {
    let regx = /(?<=data:image).*?(?=;base64)/; // 正则表示时在用于replace时,根据浏览器的不同,有的需要为字符串
    base64Data = base64Data.replace(regx, '/gif');
  }
  canvas = null;
  ctx2 = null;
  return base64Data;
}
export default onPaste;

getBase64.js

处理上传的图片

function getBase64(file) {
	function compress(img, type, maxHeight, flag) {
	  let canvas = document.createElement('canvas');
	  let ctx2 = canvas.getContext('2d');let ratio = img.width / img.height;
	  let width = img.width, height = img.height;
	  // 根据flag判断是否压缩图片
	  if (flag) {
	    // 压缩后的图片展示在输入框
	    height  = maxHeight;
	    width = maxHeight * ratio; // 维持图片宽高比
	  }
	  canvas.width = width;
	  canvas.height = height;
	​
	  ctx2.fillStyle = '#fff';
	  ctx2.fillRect(0, 0, canvas.width, canvas.height);
	  ctx2.drawImage(img, 0, 0, width, height);let base64Data = canvas.toDataURL(type, 0.75);if (type === 'image/gif') {
	    let regx = /(?<=data:image).*?(?=;base64)/; // 正则表示时在用于replace时,根据浏览器的不同,有的需要为字符串
	    base64Data = base64Data.replace(regx, '/gif');
	  }
	  canvas = null;
	  ctx2 = null;
	  return base64Data;
	}
	return new Promise(function(resolve, reject) {
		const reader = new FileReader()
		reader.readAsDataURL(file)
		reader.onload = function () {
			const result = reader.result
			let img = new Image()
			img.onload = function () {
				let compressedDataUrl = compress(img, file.type, 90, true)
				let url = compress(img, file.type, 90, false)
				img = null;
				resolve({
					data: file,
					compressedDataUrl,
					url,
					type: 'image'
				})
			}
			img.src = result
		},
		reader.onerror = function (error) {
			reject(error)
		}
	})
}

注意点

  • 图片必须是截图或者打开图片后点复制(图片必须在剪切板中才能复制)
  • 一定要设置默认focus,否则上传图片,图片不能插入到输入框
  • 前端显示 message 的时候,需要在前端加样式style=“white-space:pre-line”
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值