1、环境配置
开发语言:php+H5
框架:thinkPHP3.2+WeUI
服务器:阿里云centos7(客户主机是WD的Windows虚拟主机)
2、场景描述
最近在做微信公众号的全栈开发,涉及到一个图片上传水印的功能。因为使用的是tp框架,所以其实框架内部里面集成好了一些很方便的图片处理的工具类,只要开启php相关扩展就好了。但是由于虚拟主机受限,加上服务器性能较差,因此决定在网页前端使用H5的一些API来进行图片的处理。
说到这里,其实我应该去对比一下后端与前端处理的速度和性能的,还有一点就是如果使用后端来处理图片的话,那么对兼容性的要求就低了很多,前端只要负责显示、预览图片就好了。
3、注意
还有很重要的一点,这样做只能在每次选择一张图片的时候进行水印的添加,如果是ios或者桌面那种,一次性可以添加多张图片的话,批量加水印就没办法做了。而且据我实现,去除水印这个功能,压缩很快,所以加载水印这个还是挺耗时的。
体验地址:上传demo
4、具体实现
1、上传容器创建
创建一个上传的input标签,capture属性的设置是用来解决在安卓机器中上传图片无法调起手机拍照工具的问题,IOS并没有这个问题。accept设置文件的类型,此处设置为所有图片格式。
<input id="uploaderInput" class="weui_uploader_input" type="file" accept="image/*" multiple="" capture="camera">
2、图片压缩
图片压缩使用的是https://github.com/think2011/localResizeIMG的插件(感恩开源)。操作简单,设备兼容性还不错。
//这里的file是一个图片文件对象,可以使用files属性去获取input内的图片的对象。
lrz(file,{width:640,quality:0.5}).then(function (rst) {
//do something
var newBaseCode = rst.base64
})
压缩成功后,重新获取图片的base64的编码值。其实原理就是通过canvas对图片进行一系列裁剪缩放等操作。主要难点是处理移动端设置的兼容性。
3、图片加水印
因为我们通过上诉压缩工具得到的是图片的base64的编码,但是要对图片进行水印操作,那么需要得到图片的长宽的具体参数,因此我们就需要把base64转成一张图片,
//处理已经压缩完毕得到的图片文件(注意不是base64编码)
canvas = document.createElement('canvas');
canvas.height = img.height;
canvas.width = img.width;
var ctx = canvas.getContext("2d");
ctx.drawImage(img,0,0);
ctx.font = "20px microsoft yahei";
ctx.fillStyle = "rgba(255,255,255,0.5)";
ctx.fillText(siteTitle,30,30);
var dataUrl = canvas.toDataURL();//base64文件
rst_files.push({"rst_64":dataUrl});
//新建一个图片对象
var img = new Image();
var rst_url = convertBase64UrlToBlob(rst.base64);
if (url) {
src = url.createObjectURL(rst_url);
} else {
src = e.target.result;
}
//给图片设置src属性
img.src = src;
//base64转file
function convertBase64UrlToBlob(urlData){
var bytes=window.atob(urlData.split(',')[1]); //去掉url的头,并转换为byte
//处理异常,将ascii码小于0的转换为大于0
var ab = new ArrayBuffer(bytes.length);
var ia = new Uint8Array(ab);
for (var i = 0; i < bytes.length; i++) {
ia[i] = bytes.charCodeAt(i);
}
return new Blob( [ab] , {type : 'image/png'});
}
这里有一个很重要的地方就是,image.onload一定要写在img.src的前面,因为这样onload才会生效。具体原因自行google。
4、献上上传的完整代码
<script>
$(function(){
var name,phone,miaomuId;
var rst_files = [];
var i,src_ori;
var canvas;
var tmpl = '<li class="weui_uploader_file" style="background-image:url(#url#)"></li>',
$gallery = $("#gallery"), $galleryImg = $("#galleryImg"),
$uploaderInput = $("#uploaderInput"),
$uploaderFiles = $("#uploaderFiles");
$uploaderInput.on("change", function(e){
var src, url = window.URL || window.webkitURL || window.mozURL, files = e.target.files;
// console.log(rst_files);
// console.log(rst_files.length);
if(rst_files.length>8){
$.alert('最多上传9张图片',siteTitle);
return false;
}
// console.log(files);
for (var i = 0, len = files.length; i < len; ++i) {
var file = files[i];
console.log(i);
//图片加水印
var img = new Image();
img.onload=function () {
console.info('onload');
if(this.complete){
canvas = document.createElement('canvas');
canvas.height = img.height;
canvas.width = img.width;
var ctx = canvas.getContext("2d");
ctx.drawImage(img,0,0);
ctx.font = "20px microsoft yahei";
ctx.fillStyle = "rgba(255,255,255,0.5)";
ctx.fillText(siteTitle,30,30);
var dataUrl = canvas.toDataURL();//base64文件
//把所有的base64编码的图片存储如一个json数组中。切记这边要用双引号,规范!!!
rst_files.push({"rst_64":dataUrl});
}
console.log(rst_files);
}
//压缩并且准备添加水印
console.info(file);
lrz(file,{width:640,quality:0.5}).then(function (rst) {
console.info(file);
var rst_url = convertBase64UrlToBlob(rst.base64);
if (url) {
src = url.createObjectURL(rst_url);
} else {
src = e.target.result;
}
img.src = src;
console.info('src'+i);
});
if (url) {
src_ori = url.createObjectURL(file);
} else {
src_ori = e.target.result;
}
$uploaderFiles.append($(tmpl.replace('#url#', src_ori)));
}
});
$uploaderFiles.on("click", "li", function(){
$galleryImg.attr("style", this.getAttribute("style"));
$gallery.fadeIn(100);
});
$gallery.on("click", function(){
$gallery.fadeOut(100);
});
//base64转file
function convertBase64UrlToBlob(urlData){
var bytes=window.atob(urlData.split(',')[1]); //去掉url的头,并转换为byte
//处理异常,将ascii码小于0的转换为大于0
var ab = new ArrayBuffer(bytes.length);
var ia = new Uint8Array(ab);
for (var i = 0; i < bytes.length; i++) {
ia[i] = bytes.charCodeAt(i);
}
return new Blob( [ab] , {type : 'image/png'});
}
// 上传
$('#upload').on('click',function () {
console.log(rst_files);
miaomuId = {$id};
name = $('#name').val();
phone = $('#phone').val();
if(name == ''){
$.alert('请填写姓名',siteTitle);
return false;}
if(phone == ''){
$.alert('请填写联系电话',siteTitle);
return false;}
if(checkMobile(phone) === false){
$.alert('手机号码格式有误,请重新输入',siteTitle);
return false;}
function checkMobile($tel){if(!(/^1[3|4|5|7|8][0-9]\d{4,8}$/.test($tel))){return false;}}
if($uploaderInput[0].files.length == 0){
$.alert('请选择要上传的图片',siteTitle);
return false;}
var param = {
'name':name,
'phone':phone,
'miaomuId':miaomuId,
'rst':rst_files
};
console.log(param);
$.showLoading('上传中...');
$.ajax({
// TODO 替换jrmm
url: 'your url',
type: 'POST',
data: param,
// cache: false,
// processData: false,
// contentType: false,
success:function (response) {
$.hideLoading();
console.log(response);
$.alert(response.reason,siteTitle);
},
error:function () {
$.alert(serverError,siteTitle);
}
});
})
});
</script>
5、后端处理
部分关键代码
<?php
$base64Arr = $_POST['rst'];
$miaomuId = I('miaomuId');
$name = trim(I('name'));
$phone = I('phone');
$openid = session('openId');
if(!$miaomuId || !$name || !$phone || !$openid){
$output = outputHandler(null,0,'图片上传失败');
$this->ajaxReturn($output);
}
$picModel = D('PicRecord');
$saveRes = $this->saveImgHandler($picModel,$base64Arr,$miaomuId);
//图片上传处理
private function saveImgHandler($picModel,$base64Arr,$miaomuId = 0){
// $picModel = D('PicRecord');
$urlList = array();
$uuChar= 'A';
//保存上传的图片
foreach ($base64Arr as $file){
$uuChar .= 'A';
$type_limit = array('jpg','jpeg','png');
if(preg_match('/data:\s*image\/(\w+);base64,/iu',$file['rst_64'],$tmp)){
if(!in_array($tmp[1],$type_limit)){
return outputHandler(null,0,'图片格式不正确,只支持jpg,jpeg,png!',200);
}
}else{
return outputHandler(null,0,'上传失败,请重新再试!',200);
}
$img = str_replace(' ','+',$file['rst_64']);
$img = str_replace($tmp[0], '', $img);
//对上传的base64编码进行处理后
$hi = base64_decode($img); //解码
$rootPath = './Uploads/Home/'; //设置文件上传路径
$destination = $rootPath.date('Ym').'/'.date('d').'/';//创建文件夹
!is_dir($destination) && mkdir($destination, 0755, true);
$filename = md5(session('openId').time().$uuChar).'.jpg';.//设置文件的名称
file_put_contents($destination.$filename,$hi);//存储图片
$urlList[] = $picModel->saveUploadImgs(''.ltrim($destination,'.').$filename,$miaomuId);
}
return $urlList;
}
?>