实现回车发送
粘贴文本
粘贴图片
总结
上一篇中,我们初步新建了一个可“双向绑定”的聊天输入框,结合实际使用的场景,如果仅仅只是实现了“双向绑定”还不够,还必须处理粘贴文字、图片等功能。本文就在此基础上,继续探讨如何实现回车发送、粘贴文本(包括HTML)、粘贴图片等功能。话不多说,咱们开整。contenteditable的元素如何实现“双向绑定”?
实现回车发送在一个可编辑元素中,默认回车就是换行。但是如果我们要实现回车是“发送”的功能,需要怎么处理呢?
思路:监听键盘事件,如果判断用户按下的是回车键,则执行发送逻辑,并阻止浏览器的默认行为。
实现起来也可以有两种方式:
event
<div class="input-box" contenteditable @keydown="handleKeydown"></div>
...
handleKeydown(event) {
if (event.shiftKey && event.keyCode === 13) {
document.execCommand('insertLineBreak'); // 换行
} else if (event.keyCode === 13) { // 回车键
console.log("回车发送");
event.preventDefault();
return false;
}
}
这样就可以实现回车发送。但是这种方式是有问题的,具体如下:
如果持续按下回车键,将会一直触发
其他键 + 回车键也会触发此判断
需要额外增加逻辑,否则输入框将不可换行
那有没有什么办法是可以解决的呢,对于Vue项目来说,可以借助按键修饰符
按键修饰符
<div class="input-box" contenteditable @keyup.enter.exact="submit" @keydown.enter.exact="handleKeydown"></div>
...
},
submit() {
console.log('回车发送');
},
handleKeydown(event) {
event.preventDefault();
return false;
},
按键修饰符的具体文档可参考官网:事件处理 — Vue.js (vuejs.org),本处只重点说明.extra
修饰符。.extra
可以精确控制按键组合,结合keyup
和keydown
,上述代码翻译过来就是:只有一个回车键时,并且回弹起来才会触发“回车发送”;如果只有一个回车键,并且按下,则阻止浏览器默认事件。这样就可以实现“回车发送”。也许有人会问,换行怎么办。答案就是使用shift + enter
,这也是默认的换行。如果不喜欢shift+enter
就需要自己处理换行了。
优点
使用方式简单
可使用默认的方式换行
粘贴文本粘贴文本不是直接使用ctrl + v
嘛,这都不知道?话是这么说,但是如果我们相对粘贴的文本做些处理,直接使用ctrl + v
就不合适了,而且后面还会用来粘贴图片。想要直接粘贴图片并显示,还没有原生输入框可做到吧。本处需要使用paste
事件,也就是专门处理粘贴的。由于我们是要手动插入数据,因此需要阻止默认操作,需要借助prevent
事件修饰符。
paste.prevent
<div class="input-box" contenteditable @paste.prevent="onPaste"></div>
...
import onPaste from '@/utils/onPaste.js'; // 引入专门处理粘贴的方法
...
// 注意是异步处理
async onPaste(event) {
const result = await onPaste(event); // 传入粘贴事件
console.log('处理后的数据', result);
// 插入文本
document.execCommand('insertText', false, result.data);
}
重点来了
新建onPaste.js
,用来处理文本和图片的粘贴
// onPaste.js
// 定义粘贴函数
const onPaste = (event) => {
// 剪贴板没数据,则直接返回
if (!event.clipboardData || !event.clipboardData.items) {
return;
}
// 封装Promise
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 === 'string') {
// 方式一,直接返回粘贴的文本数据
// let str = event.clipboardData.getData('text');
// resovle({
// data: str
// });
// 方式二,过滤掉粘贴的文本中存在的html标签和换行等字符
let reg = /</?.+?/?>/g; // 匹配粘贴文本中的html标签
// 处理字符串类型,参数为回调函数
item.getAsString(str => {
resovle({
data: str.replace(reg, '').replace(/(\r\n)|(\n)/g, '') // 去掉换行符
})
})
} else {
reject(new Error('不支持粘贴该类型'));
}
}
})
}
export default onPaste; // 默认导出方法
关于剪贴板,由几个概念需要提前了解:clipboardData
、DataTransfer
、DataTransferItem
,具体可参考clipboardData。运行结果如下:
注:本次复制的是<div data-v-66ea8951="">父组件修改子组件的值</div>
粘贴图片和粘贴问题有相同之处,也有所不同。最大的不同是显示图片,需要获取图片的src
。在写代码前先想几个问题:
图片展示需要提供src,那么如何获取src
图片可大可小,如何限制图片的尺寸,避免超出输入框范围
图片像素很高,是否需要压缩
有了这些问题,就需要一一解决。
图片的压缩,采用canvas绘制指定尺寸的图片
base64
就由canvas的toDataURL
方法获得
压缩时,维持图片的宽高比
话不多说,先看代码:
// DivEditable.vue
async onPaste(event) {
const result = await onPaste(event);
console.log('处理后的粘贴数据', result);
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.src = result.compressedDataUrl; // 使用压缩后的图片
range.insertNode(img);
range.collapse(false);
sel.removeAllRanges();
sel.addRange(range);
}
}
}
// onPaste.js
// 定义粘贴函数
const onPaste = (event) => {
...
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 {
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, maxWidth, 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) {
// 压缩后的图片展示在输入框
width = maxWidth;
height = maxWidth / 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;
代码实现效果图:
插入图片可使用insertImage
命令插入,也可以使用Selection
来插入。效果都差不多
压缩图片,其实也就是用canvas根据尺寸比例,重新绘制一张图片,并获取图片的base64
返回数据时,需要返回压缩后和原图。
如果必须要压缩图片,则在handleImage方法中,直接使用FileReader加载图片后,将base64编码返回即可。
总结本文实现了回车发送、粘贴文本、粘贴图片的功能
涉及事件修饰符、按键修饰符、剪贴板、canvas等知识
项目地址
以上就是Vue实现输入框回车发送和粘贴文本与图片功能的详细内容,更多关于Vue输入框粘贴文本图片的资料请关注软件开发网其它相关文章!