mui开发app之cropper裁剪后上传头像的实现
应广大读者建议,已经将该项目源码提交到地址:
https://github.com/devilyouwei/dashen
在大多数app项目中,都需要对用户头像的上传,之前做web开发的时候,我主要是通过input type=file的标签实现的,上传后,使用php对图片进行裁剪,这种方式比较传统简单。
此次app开发中需要做到用户选择本地相册或者进行拍照->对照片进行裁剪->最后同时更新本地头像和服务器端的图片。(app常见套路)
我将要结合:mui,cropper,jquery开发!
实现思路:
1.用户点击头像,打开actionsheet
2.选择图片或者拍照后返回的图片绝对地址传入单独的裁剪页面,跳转到裁剪页面
3.裁剪页面裁剪后选择确认则将裁减后图片保存到localstorage中(其实是把整个图片保存到本地数据库中)
4.触发一个更新上一页头像的事件,返回上一页
5.看到图片已经更改
6.保存,图片上传到服务器(json)
7.服务器将图片的base64保存到数据库(text类型),实现数据库保存图片,当然可以使用后端语言的方法还原为图片后保存在文件系统中(建议:小图可保存在数据库中,大图保存在文件系统)
实现工具和插件,直接看代码:
js
<script type="text/javascript" src="../js/jquery-1.11.2.min.js"></script> <script src="../js/mui.min.js"></script> <script type="text/javascript" src="../js/exif.js"></script> <script src="../js/cropper.min.js"></script> <script src="../js/fastclick.js"></script>
css
<link href="../css/mui.min.css" rel="stylesheet" /> <link href="../css/iconfont.css" rel="stylesheet" /> <link href="../css/cropper.css" rel="stylesheet" />
这里我们主要使用了一个cropper.js的插件,自行百度下载,目前比较好用的插件只有这个,大神的话自己写一个吧
注意cropper必须使用jquery,而jquery比较臃肿,在其他mui中尽量不要引入,我也是迫不得已,使用jq并非我本意
fastclcik.js是加速点击触发,一般在手机端开发其实用不到,因为一般使用tap,这里我加了进去,似乎对裁剪图片时手势操作有帮助,(+_+)?,不加也没有影响,测试过
iconfont是按钮图标
exif.js是可以检测图片拍摄时采用横向还是纵向
这几个请自行百度下载!
接下来可以开发了:
一共两个页面,
headinfo.html——编辑页面
cropper.html——裁剪页面
headinfo.html
<!doctype html> <html> <head> <meta charset="UTF-8"> <title>我的资料</title> <meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=1,user-scalable=no"> <link href="../css/mui.min.css" rel="stylesheet" /> <link href="../css/style.css" rel="stylesheet" /> <style type="text/css"> .head { height: 40px; } #head { line-height: 40px; } .head-img { width: 40px; height: 40px; } #head-img1 { position: absolute; bottom: 10px; right: 40px; width: 40px; height: 40px; } .mui-fullscreen { position: fixed; z-index: 20; background-color: #000; } .mui-content { position: absolute; left: 0; right: 0; top: 0; bottom: 0; } </style> </head> <body> <header class="mui-bar mui-bar-nav"> <button type="button" id="finish" class="mui-left mui-btn mui-btn-link mui-btn-nav mui-pull-left"> <span class="mui-icon mui-icon-left-nav"></span>完成 </button> <h1 class="mui-title">头信息</h1> </header> <div class="mui-content"> <ul class="mui-table-view"> <li class="mui-table-view-cell"> <a id="head" class="mui-navigate-right">个人头像 <span class="mui-pull-right head"> <img class="head-img mui-action-preview" id="head-img1" src=""/> </span> </a> </li> </ul> <div class="mui-input-row mui-margin-vertical"> <h5 class="mui-margin-horizontal-xs">个人签名:</h5> <input class="mui-padding-lg" id="signature" type="text" placeholder="个人签名" /> </div> </div> <script src="../js/mui.min.js"></script> <script src="../js/app.js"></script> <script type="text/javascript"> (function($) { mui.init({ swipeBack: true }); $.plusReady(function() { //初始化头像,和预览图 setTimeout(function() { defaultInfo(); setTimeout(function() { initImgPreview(); }, 200); }, 0); //弹出菜单 mui(".mui-table-view-cell").on("tap", "#head", function(e) { if(mui.os.plus) { var a = [{ title: "拍照" }, { title: "从手机相册选择" }]; plus.nativeUI.actionSheet({ title: "修改头像", cancel: "取消", buttons: a }, function(b) { switch(b.index) { case 0: break; case 1: getImage(); break; case 2: galleryImg(); break; default: break } }) } }); //完成并返回 document.getElementById("finish").addEventListener("tap", function() { var newSign = document.getElementById("signature").value; var oldSign = app.getUserInfo().signature; if(oldSign === newSign) $.back(); else { app.request("User", "updateSignature", { 'signature': newSign }, function(res) { if(res.login == 0) { mui.toast(res.info); app.clearToken(); app.toLogin(); return false; } if(res.status == 1) { app.setSignature(newSign); mui.toast(res.info); var view = plus.webview.getWebviewById("index"); $.fire(view, "updateHeadInfo"); $.back(); } else { mui.toast(res.info); } }); } }); }); //拍照 function getImage() { var c = plus.camera.getCamera(); c.captureImage(function(e) { //存储到本地 plus.io.resolveLocalFileSystemURL(e, function(entry) { cutImage(entry.toLocalURL()); }, function(e) { console.log("读取拍照文件错误:" + e.message); }); }, function(s) { console.log("error" + s); }, { filename: "_doc/head.jpg" }) } //相册 function galleryImg() { plus.gallery.pick(function(a) { plus.io.resolveLocalFileSystemURL(a, function(entry) { //entry为图片原目录(相册)的句柄 cutImage(entry.toLocalURL()); }, function(e) { console.log("读取图片错误:" + e.message); }); }, function(a) {}, { filter: "image" }) }; //设置默认头像 function defaultInfo() { var my_icon = app.getHeadImg(); //头像 var signature = app.getUserInfo().signature; //签名 if(my_icon && my_icon != "") { document.getElementById("head-img1").src = my_icon; } else { document.getElementById("head-img1").src = '../images/my_icon.jpg'; } if(signature && signature != "") { document.getElementById("signature").value = signature; } else { document.getElementById("signature").value = ""; } } //预览图像 document.getElementById("head-img1").addEventListener('tap', function(e) { e.stopPropagation(); //阻止冒泡 }); function initImgPreview() { var imgs = document.querySelectorAll("img.mui-action-preview"); imgs = mui.slice.call(imgs); if(imgs && imgs.length > 0) { var slider = document.createElement("div"); slider.setAttribute("id", "__mui-imageview__"); slider.classList.add("mui-slider"); slider.classList.add("mui-fullscreen"); slider.style.display = "none"; slider.addEventListener("tap", function() { slider.style.display = "none"; }); slider.addEventListener("touchmove", function(event) { event.preventDefault(); }) var slider_group = document.createElement("div"); slider_group.setAttribute("id", "__mui-imageview__group"); slider_group.classList.add("mui-slider-group"); imgs.forEach(function(value, index, array) { //给图片添加点击事件,触发预览显示; value.addEventListener('tap', function() { slider.style.display = "block"; _slider.refresh(); _slider.gotoItem(index, 0); }) var item = document.createElement("div"); item.classList.add("mui-slider-item"); var a = document.createElement("a"); var img = document.createElement("img"); img.setAttribute("src", value.src); a.appendChild(img) item.appendChild(a); slider_group.appendChild(item); }); slider.appendChild(slider_group); document.body.appendChild(slider); var _slider = mui(slider).slider(); } } //裁剪图片 function cutImage(path) { $.openWindow({ url: 'cropper.html', id: 'cropper', extras: { path: path, }, show: { aniShow: 'zoom-fade-in', duration: 800 }, waiting: { autoShow: true } }); } //更新用户头像 function update_head_img(e) { var my_icon = e.detail.img; //先上传 app.request("User", "saveHeadImg", { 'my_icon': my_icon }, function(res) { if(res.login == 0) { mui.toast(res.info); app.clearToken(); app.toLogin(); return false; } if(res.status == 1) { app.setHeadImg(my_icon); //确认上传成功后存储到本地 if(my_icon == "") my_icon = "../images/my_icon.jpg"; document.getElementById("head-img1").src = my_icon; //刷新小图 document.querySelector("#__mui-imageview__group .mui-slider-item img").src = my_icon; //刷新预览图 //顺带触发上一个页面的updateHeadInfo var view = plus.webview.getWebviewById("index"); $.fire(view, "updateHeadInfo"); mui.toast(res.info); } else { mui.toast(res.info); } }); } window.addEventListener("updateHeadImg", update_head_img); //添加自定义事件,其他页面调用 })(mui); </script> </body> </html>
cropper.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>裁剪头像</title> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <link href="../css/mui.min.css" rel="stylesheet" /> <link href="../css/cropper.css" rel="stylesheet" /> <style type="text/css"> body { background-color: #000000; } #cropper-example-1 { background-color: #000000; height: 93%; width: 100%; position: absolute; } .divbut { width: 100%; text-align: center; position: fixed; z-index: 2; bottom: 0px; background-color: #000000; height: 7.5%; line-height: 50px; } .divbut>div:first-child { float: left; width: 20%; } .divbut>div:last-child { float: right; width: 20%; } img#im { height: 100%; width: 100%; } </style> </head> <body> <div id="cropper-example-1" class="mui-hidden"> <img id="im" alt="Picture" /> </div> <div class="divbut"> <div> <p id="quxiao" class="mui-icon mui-icon-closeempty"></p> </div> <div> <p id="xuanqu" class="mui-icon mui-icon-checkmarkempty"></p> </div> </div> <img src="" alt="" class="mui-hidden" id="im_exif" /> <script type="text/javascript" src="../js/jquery-1.11.2.min.js"></script> <script src="../js/mui.min.js"></script> <script type="text/javascript" src="../js/exif.js"></script> <script src="../js/cropper.min.js"></script> <script src="../js/fastclick.js"></script> <script src="../js/app.js"></script> <script> $(function() { //尽量用一下fastclick FastClick.attach(document.body); ! function() { var i = { aspectRatio: 1 / 1 }; }() }); (function(c) { var Cro = function() {} c.extend(Cro.prototype, { orientation: null, urldata: null, view: null, num: 0, sbx: null, sby: null, n: 0, onReady: function() { var that = this; mui.init(); that.bindEvent(); that.view = plus.webview.currentWebview(); var img = document.getElementById("im_exif"); img.src = that.view.path; img.addEventListener("load", function() { //exif调整图片的横竖 EXIF.getData(this, function() { var orientation = EXIF.getAllTags(this).Orientation; $("#im").attr("src", that.loadcopyImg(img, orientation)); document.getElementById("cropper-example-1").classList.remove("mui-hidden"); //显示裁剪区域 that.cropperImg(); }); }) }, cropperImg: function() { var that = this; $('#cropper-example-1 > img').cropper({ aspectRatio: 1 / 1, autoCropArea: 1, strict: true, background: false, guides: false, highlight: false, dragCrop: false, movable: false, resizable: false, crop: function(data) { that.urldata = that.base64(data); } }); }, loadcopyImg: function(img, opt) { var that = this; var canvas = document.createElement("canvas"); var square = 500; var imageWidth, imageHeight; if(img.width > img.height) { imageHeight = square; imageWidth = Math.round(square * img.width / img.height); } else { imageHeight = square; //this.width; imageWidth = Math.round(square * img.width / img.height); } canvas.height = imageHeight; canvas.width = imageWidth; if(opt == 6) { that.num = 90; } else if(opt == 3) { that.num = 180; } else if(opt == 8) { that.num = 270; } if(that.num == 360) { that.num = 0; } var ctx = canvas.getContext("2d"); ctx.translate(imageWidth / 2, imageHeight / 2); ctx.rotate(that.num * Math.PI / 180); ctx.translate(-imageWidth / 2, -imageHeight / 2); ctx.drawImage(img, 0, 0, imageWidth, imageHeight); var dataURL = canvas.toDataURL("image/jpeg", 1); return dataURL; }, bindEvent: function() { var that = this; document.getElementById("quxiao").addEventListener("tap", function() { mui.back(); //取消就直接返回 }); document.getElementById("xuanqu").addEventListener("tap", function() { //触发上一个页面刷新图片事件 var preView = plus.webview.getWebviewById('user/headinfo'); mui.fire(preView, 'updateHeadImg', { 'img': that.urldata }); //不能保存图片,需要判断上传性,所以选择传值的方式,传递图片,格式为json mui.back(); }); }, base64: function(data) { var that = this; var img = document.getElementById("im"); var canvas = document.createElement("canvas"); //像素 canvas.height = 400; canvas.width = 400; var bx = data.x; var by = data.y; var ctx = canvas.getContext("2d"); ctx.drawImage(img, bx, by, data.width, data.height, 0, 0, 400, 400); var dataURL = canvas.toDataURL("image/jpeg", 0.5); //第二个参数是质量 return dataURL; } }); var cro = new Cro(); c.plusReady(function() { cro.onReady(); }) })(mui) </script> </body> </html>
一开始对于headinfo.html我是打算先将拍照和相册获取的图片使用plus的io保存到项目app的_doc目录下再编辑裁剪,但是发现可以精简,直接把图片的绝对地址传到cropper中,直接编辑后保存为base64,放在localstorage中,以后需要头像的地方直接从localstorage中获取即可!我在app.js(这是我写的项目的公共js类库,处处引用)
/* *这只是其中一部分,在裁剪过程中需要用到的库函数 *主要是方便,随时可以获取保存在localstorage中的头像base64图 */ (function($, owner) { // 获取用户个人信息 owner.getUserInfo = function() { var userText = localStorage.getItem('$user') || "{}"; return JSON.parse(userText); } // 存储用户个人信息 owner.setUserInfo = function(user) { user = user || {}; localStorage.setItem('$user', JSON.stringify(user)); } // 获取用户头像 owner.getHeadImg = function() { var headImg = owner.getUserInfo().my_icon || ""; return headImg; } //设置用户头像 owner.setHeadImg = function(baseData) { var userInfo = owner.getUserInfo(); userInfo.my_icon = baseData; //只对my_icon这一项进行修改,其他不动 owner.setUserInfo(userInfo); } }(mui, window.app = {}));
base64的图片格式是html5 canvas所使用的图片格式,可以直接放入img的src中!
注:如果想使用js直接在手机端将base64转换为图片文件请看:mui开发app之js将base64转图片文件
效果图: