js实现ctrl+v粘贴上传图⽚(兼容chrome、firefox、
ie11)
我们或多或少都使⽤过各式各样的富⽂本编辑器,其中有⼀个很⽅便功能,复制⼀张图⽚然后粘贴进⽂本框,这张图⽚就被上传了,那么这个⽅便的功能是如何实现的呢?
原理分析
提取操作:复制=>粘贴=>上传
在这个操作过程中,我们需要做的就是:监听粘贴事件=>获取剪贴板⾥的内容=>发请求上传
为⽅便理解下⽂,需要先明⽩⼏点:
我们只能上传⽹页图(在⽹页上右键图⽚,然后复制)和截图(截图⼯具截的图⽚,eg:qq截图),不能粘贴上传系统⾥的图⽚(从桌⾯上、硬盘⾥复制),他们是存在完全不同的地⽅的。
截图⼯具截的图与在⽹页点击右键复制的图是有些不同的,因此处理⽅式也不⼀样。
知悉paste event这个事件:当进⾏粘贴(右键paste/ctrl+v)操作时,该动作将触发名为'paste'的剪贴板事
件,这个事件的触发是在剪贴板⾥的数据插⼊到⽬标元素之前。如果⽬标元素(光标所在位置)是可编辑的元素(eg:设置了
contenteditable属性的div。textarea并不⾏。),粘贴动作将把剪贴板⾥的数据,以最合适的格式,插⼊到⽬标元素⾥;如果⽬标元素不可编辑,则不会插⼊数据,但依然触发paste event。数据在粘贴的过程中是只读的。此段是翻译于w3.org_the-paste-action。
可惜的是,经过试验,发现chrome(当前最新版)、firefox(当前最新版)、ie11对paste事件的实现并不是完全按照w3c来的,各⾃也有区别(w3c的paste标准也因此只是草案阶段)。
test代码及截图如下:
chrome:
<textarea ></textarea>
<div contenteditable >
</div>
<script>
document.addEventListener('paste', function (event) {
console.log(event)
})
</script>
1. event有clipboardData属性,且clipboardData有item属性,clipboardData.item中的元素(对象)有type和kind属性;
2. ⽆论在哪进⾏粘贴,均可触发paste事件;
3. 在div(未特殊声明时,本⽂div均指设置了contenteditable属性的div)⾥粘贴截图,不显⽰图⽚。img.src为base64编码
字符串;
4. 在div⾥粘贴⽹页图⽚,直接显⽰图⽚,img.src为图⽚地址。
firefox:
1. event有clipboardData属性,clipboardData没有item属性;
2. 只有在textarea⾥或者可编辑的div(⾥才粘贴才触发paste事件;
3. 在div⾥粘贴截图,直接显⽰图⽚,img.src为base64编码字符串;
4. 在div⾥粘贴⽹页图⽚,表现同chrome。
ie11:(不截图了,可⾃⾏试验,其他浏览器同理<( ̄▽ ̄)/,因为懒...)
event没有clipboardData属性;
只在可编辑的div中粘贴才触发paste事件;
在div⾥粘贴截图,直接显⽰图⽚,img.src为base64编码字符串;
在div⾥粘贴⽹页图⽚,表现同chrome。
监听了paste事件,也知道了表现形式,接下来就是如何获取数据了:
chrome有特定的⽅法,利⽤clipboardData.items、getAsFile()、new FileReader()等api可以在paste回调
函数⾥获取到剪贴板⾥图⽚的base64编码字符串(⽆论是截图粘贴的还是⽹页图⽚复制粘贴的),ie11,firefox没有这样的api,不过依然有办法可以获取,因为数据已经表现在img的src⾥了,对于截图粘贴的,直接取img的src属性值(base64),对于⽹页粘贴的,则把地址传给后台,然后根据地址down下来,存在⾃⼰的服务器,最后把新地址返回来交给前端展⽰就ok了。为了保持⼀致性便于管理,统⼀将所有情况(截图、⽹页)中的img的src属性替换为⾃⼰存储的地址。因此可以得到以下核⼼代码(注释很全哦~~):
html展⽰:
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
body {
display: -webkit-flex;
display: flex;
-webkit-justify-content: center;
justify-content: center;
}
#tar_box {
width: 500px;
height: 500px;
border: 1px solid red;
}
</style>
前端js处理逻辑:
document.addEventListener('paste', function (event) {
console.log(event)
var isChrome = false;
if ( event.clipboardData || iginalEvent ) {
//not for ie11 某些chrome版本使⽤的是iginalEvent
var clipboardData = (event.clipboardData || iginalEvent.clipboardData);
if ( clipboardData.items ) {
// for chrome
var items = clipboardData.items,
len = items.length,
blob = null;
isChrome = true;
//items.length⽐较有意思,初步判断是根据mime类型来的,即有⼏种mime类型,长度就是⼏(待验证)
//如果粘贴纯⽂本,那么len=1,如果粘贴⽹页图⽚,len=2, items[0].type = 'text/plain', items[1].type = 'image/*'
//如果使⽤截图⼯具粘贴图⽚,len=1, items[0].type = 'image/png'
//如果粘贴纯⽂本+HTML,len=2, items[0].type = 'text/plain', items[1].type = 'text/html'
// console.log('len:' + len);
// console.log(items[0]);
// console.log(items[1]);
// console.log( 'items[0] kind:', items[0].kind );
// console.log( 'items[0] MIME type:', items[0].type );
// console.log( 'items[1] kind:', items[1].kind );
// console.log( 'items[1] MIME type:', items[1].type );
//阻⽌默认⾏为即不让剪贴板内容在div中显⽰出来
event.preventDefault();
//在items⾥粘贴的image,据上⾯分析,需要循环
for (var i = 0; i < len; i++) {
if (items[i].type.indexOf("image") !== -1) {
// console.log(items[i]);
// console.log( typeof (items[i]));
//getAsFile() 此⽅法只是living standard firefox ie11 并不⽀持
blob = items[i].getAsFile();
}
}
if ( blob !== null ) {
var reader = new FileReader();
// sult 即为图⽚的Base64编码字符串
var base64_str = sult
//可以在这⾥写上传逻辑直接将base64编码的字符串上传(可以尝试传⼊blob对象,看看后台程序能否解析)
uploadImgFromPaste(base64_str, 'paste', isChrome);
}
}
} else {
//for firefox
setTimeout(function () {
//设置setTimeout的原因是为了保证图⽚先插⼊到div⾥,然后去获取值
var imgList = document.querySelectorAll('#tar_box img'),
len = imgList.length,
src_str = '',
i;
for ( i = 0; i < len; i ++ ) {
if ( imgList[i].className !== 'my_img' ) {
/
/如果是截图那么src_str就是base64 如果是复制的其他⽹页图⽚那么src_str就是此图⽚在别⼈服务器的地址 src_str = imgList[i].src;
}
}
uploadImgFromPaste(src_str, 'paste', isChrome);
}, 1);
}
} else {
//for ie11
setTimeout(function () {
var imgList = document.querySelectorAll('#tar_box img'),
len = imgList.length,
src_str = '',
i;
for ( i = 0; i < len; i ++ ) {
if ( imgList[i].className !== 'my_img' ) {
src_str = imgList[i].src;
}
}
uploadImgFromPaste(src_str, 'paste', isChrome);
}, 1);
}
})
function uploadImgFromPaste (file, type, isChrome) {
var formData = new FormData();
formData.append('image', file);
formData.append('submission-type', type);
var xhr = new XMLHttpRequest();
xhr.open('POST', '/upload_image_by_paste');
if ( adyState === 4 ) {
if ( xhr.status === 200 ) {
var data = JSON.parse( sponseText ),js验证字符串长度
tarBox = ElementById('tar_box');
if ( isChrome ) {
var img = ateElement('img');
img.className = 'my_img';
img.src = data.store_path;
tarBox.appendChild(img);
} else {
var imgList = document.querySelectorAll('#tar_box img'),
len = imgList.length,
i;
for ( i = 0; i < len; i ++) {
if ( imgList[i].className !== 'my_img' ) {
imgList[i].className = 'my_img';
imgList[i].src = data.store_path;
}
}
}
} else {
console.log( xhr.statusText );
}
};
};
console.log( xhr.statusText );
}
xhr.send(formData);
}
⽤express.js搭的简易后台的接收逻辑:
router.post('/', upload.array(), function (req, res, next) {
//1.获取客户端传来的src_str字符串=>判断是base64还是普通地址=>获取图⽚类型后缀(jpg/png etc)
//=>如果是base64替换掉"前缀"("data:image\/png;base64," etc)
//2.base64 转为 buffer对象普通地址则先down下来
//3.写⼊硬盘(后续可以将地址存⼊数据库)
//4.返回picture地址
var src_str = req.body.image,
timestamp = new Date().getTime();
if ( src_str.match(/^data:image\/png;base64,|^data:image\/jpg;base64,|^data:image\/jpg;base64,|^data:image\/bmp;base64,/) ) {
//处理截图 src_str为base64字符串
var pic_suffix = src_str.split(';',1)[0].split('/',2)[1],
base64 = place(/^data:image\/png;base64,|^data:image\/jpg;base64,|^data:image\/jpg;base64,|^data:image\/bmp;base64,/, ''), buf = new Buffer(base64, 'base64'),
store_path = 'public/images/test_' + timestamp + '.' + pic_suffix;
fs.writeFile(store_path, buf, function (err) {
if (err) {
throw err;
} else {
res.json({'store_path': store_path});
}
});
} else {// 处理⾮chrome的⽹页图⽚ src_str为图⽚地址
var temp_array = src_str.split('.'),
pic_suffix = temp_array[temp_array.length - 1],
store_path = 'public/images/test_' + timestamp + '.' + pic_suffix,
wstream = fs.createWriteStream(store_path);
request(src_str).pipe(wstream);
<('finish', function (err) {
if( err ) {
throw err;
} else {
res.json({"store_path": store_path});
}
});
}
});
需要node环境:安装node=>npm intall=>node app.js)
以上就是本⽂的全部内容,希望对⼤家的学习有所帮助。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论