在触屏设备上面利用html5裁剪图片(转)
前言
现在触屏设备越来越流行,而且大多数已经支持html5了。针对此,对触屏设备开发图片裁剪功能,
让其可以直接处理图片,减轻服务端压力。
技术点
浏览器必须支持html5,包括fileReader,canvas等api,并且该设备至少支持单点触事件(touchstart,touchmove,touchend),可惜的是
很多浏览器只能识别一只手指(不支持多点触摸事件,假如支持的话,请告知我)。
思路
利用filereader直接读取本地图片,然后赋予一个图片,该图片及裁剪框的位置计算跟pc端一样,但是触发的事件不一样,触屏版是根据触屏事件触发的。裁剪时,利用cavas的api直接画出相关图像,然后得到数据,再利用xmlhttprequest发送请求。
非html5无法完成这个过程。
运行结果
这只是一个demo,也是最初的雏形,当然不会太好看了,但是基本实现功能即可。
部分代码
1 <!doctype html> 2 <html> 3 4 <head> 5 <meta name="Author" content="flashlizi - www.riaidea.com"> 6 <meta name="Description" content="HTML5 experiment"> 7 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 8 <title>头像上传组件 - HTML5版</title> 9 <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" /> 10 11 <style> 12 body 13 { 14 padding: 0; 15 margin: 0; 16 height: 100%; 17 background-color: #eee; 18 font-size: 12px; 19 color: #666; 20 } 21 22 a 23 { 24 text-decoration: none; 25 color: #333; 26 } 27 28 a:hover 29 { 30 text-decoration: none; 31 color: #f00; 32 } 33 34 35 </style> 36 <script> 37 38 if(window.FileReader==undefined){ 39 alert("该手机不支持html5"); 40 } 41 42 </script> 43 44 <script type="text/javascript" src="/static/mobile/lib/zepto.min.js"></script> 45 </head> 46 47 <body > 48 <h1>选择图片:<input type="file" id="browseFile" onchange=""><input type="button" id="saveimg" value="保存图片"/></h1> 49 <div id="wrapper" style="border: 1px gray dotted; padding: 25px;"> 50 51 <div id="component_box" style="position: relative; border: 1px green solid; width: 300px; height: 300px;"> 52 <div id="tipBox" style="display: none;"> 53 <img src=""/> 54 </div> 55 <div id="mainCutter" style="overflow: hidden; display: none; position: relative;"> 56 <img id="imgPreview" /> 57 <div id="cutBox" style=" position:absolute; width: 150px; height: 200px; opacity: 0.5; background: gray;"></div> 58 </div> 59 </div> 60 61 <!--画布--> 62 63 <canvas id="cropper" style=" display:none;border:1px solid red; width: 300px; height: 300px;" ></canvas> 64 </div> 65 <div><span style="color: green;">调整裁剪区域大小:</span> 66 <!--调整用slider--> 67 <div><div id="processBar" style=" margin: 0 auto; position: relative; width: 220px; height: 20px; background: green;"><div id="processPoint" style="background: url(images/horizSlider.png); width: 18px; height: 20px; position: absolute;left: 0;top: 0;"></div></div></div> 68 </div> 69 <div id="the_show" style="display: none;"> 70 <h2>提示:</h2> 71 <div id="theTips"></div> 72 73 <h2>后台获得的图像</h2> 74 <img src="" id="showImg"/> 75 </div> 76 <div style="color: green;">友情提醒:拖动裁剪框裁剪框将随之移动,上划放大裁剪框,下滑缩小裁剪框。</div> 77 <div id="tips2" style="color: green; position: absolute;left: 0px; bottom: 0px; border: 1px solid green;"></div> 78 79 80 <script type="text/javascript"> 81 //--逻辑,点击图片上传选择后将加载预览图片 82 var Options={ 83 width:300, 84 height:300, 85 cutWidth:150, 86 cutHeight:200, 87 cutMinSize:50,//裁剪框最小尺寸,即最小可以缩放到这个size,width及height任意一个都无法小于这个值。 88 89 //--系统自带,运行时自动运算,请不要修改。 90 cropViewWidth:0,//在画布里面显示的最大宽度 91 cropViewHeight:0,//在画布里面显示的最大高度 92 cropLeft:0, 93 cropTop:0, 94 //--裁剪框 95 cutViewWidth:0, //当前宽度, 96 cutViewHeight:0,//当前高度 97 cutMaxWidth:0, //裁剪框最大宽度。 98 cutMaxHeight:0,//裁剪框最大高度。 99 //--四象限。用于判断距离。 100 cutBoxLimitX1:0, 101 cutBoxLimitX2:0, 102 cutBoxLimitY1:0, 103 cutBoxLimitY2:0, 104 cutLeft:0,//裁剪框绝对定位,左侧距离。 105 cutTop:0,//裁剪框绝对定位,离顶部距离。 106 initStatus:false//当前组件是否已经初始化了。 107 }; 108 var Options_image={ 109 width:0, 110 height:0, 111 imgData:"" 112 } 113 114 var input_browseFile = document.getElementById("browseFile"); 115 var img_preview = document.getElementById("imgPreview"); 116 var cutBox=document.getElementById("cutBox"); 117 var tipBox=document.getElementById("tipBox"); 118 var _cropper=document.getElementById("cropper"); 119 var mainCutter=document.getElementById("mainCutter"); 120 var tips2=$("#tips2"); 121 var wrapper=document.getElementById("wrapper"); 122 var component_box=document.getElementById("component_box"); 123 124 var ctx = _cropper.getContext('2d');//ctx.drawImage(myImage, 50, 50); 125 function previewInImage (file) { 126 //通过file.size可以取得图片大小 127 var reader = new FileReader(); 128 LoadingImage(); 129 130 reader.onload = function( evt ){ 131 img_preview.src = evt.target.result; 132 } 133 Options_image.imgData= reader.readAsDataURL(file); 134 } 135 img_preview.onload=function(){ 136 Options_image.width=img_preview.width; 137 Options_image.height=img_preview.height; 138 _initCropAndCut(); 139 } 140 function LoadingImage(){ 141 $(img_preview).css({"width":"","height":""}); 142 } 143 function _initCropAndCut(){ 144 //--计算比例,将其放到canvas里面。 145 146 var scale = Math.max(Options_image.width/Options.width,Options_image.height/Options.height); 147 if(scale>1){ 148 Options.cropViewWidth=parseInt(Math.floor(Options_image.width/scale)); 149 Options.cropViewHeight=parseInt(Math.floor(Options_image.height/scale)); 150 } 151 else{ 152 Options.cropViewWidth=Options_image.width; 153 Options.cropViewHeight=Options_image.height; 154 } 155 //--计算画布里面的图像的位置。 156 Options.cropLeft=parseInt((Options.width-Options.cropViewWidth)/2); 157 Options.cropTop=parseInt((Options.height-Options.cropViewHeight)/2); 158 //--计算裁剪框实际大小及实际位置。 159 //计算裁剪框的位置。 160 161 var scale_2=Math.max(Options.cutWidth/Options.cropViewWidth,Options.cutHeight/Options.cropViewHeight); 162 if(scale_2>1){ 163 Options.cutViewWidth=parseInt(Math.floor(Options.cutWidth/scale_2)); 164 Options.cutViewHeight=parseInt(Math.floor(Options.cutHeight/scale_2)); 165 } 166 else{ 167 Options.cutViewHeight=Options.cutHeight; 168 Options.cutViewWidth=Options.cutWidth; 169 } 170 Options.cutMaxWidth=Options.cutViewWidth; 171 Options.cutMaxHeight=Options.cutViewHeight; 172 173 Options.cutLeft=parseInt(Math.floor((Options.cropViewWidth-Options.cutViewWidth))/2); 174 Options.cutTop=parseInt(Math.floor((Options.cropViewHeight-Options.cutViewHeight))/2); 175 //-四象限。 176 Options.cutBoxLimitX1=0; 177 Options.cutBoxLimitX2=Options.cropViewWidth; 178 Options.cutBoxLimitY1=0; 179 Options.cutBoxLimitY2=Options.cropViewHeight; 180 181 $(cutBox).css({"display":"block","width":Options.cutViewWidth+"px","height":Options.cutViewHeight+"px","left":Options.cutLeft+"px","top":Options.cutTop+"px"}); 182 $(img_preview).css({"width":Options.cropViewWidth+"px","height":Options.cropViewHeight+"px"}); 183 $(mainCutter).css({"display":"block","width":Options.cropViewWidth+"px","height":Options.cropViewHeight+"px","left":Options.cropLeft+"px","top":Options.cropTop+"px"}); 184 //ctx.drawImage(img_preview,Options.cropLeft,Options.cropTop,Options.cropViewWidth,Options.cropViewHeight); 185 //ctx.drawImage(img_preview, 0, 0, Options_image.width,Options_image.height, Options.cropLeft, Options.cropTop, Options.cropViewWidth, Options.cropViewHeight ); 186 187 Options.initStatus=true; 188 Options_process.initStatus=true; 189 Options_process.percent=100; 190 Options_process.pointX=Options_process.barWidth; 191 _resizeProcessBar(); 192 } 193 194 input_browseFile.addEventListener("change", function () { 195 //通过 this.files 取到 FileList ,这里只有一个 196 previewInImage(this.files[0]); 197 198 }, false); 199 //--添加缩放功能。 200 Options_zoom={ 201 beginX1:0, 202 beginY1:0, 203 beginX2:0, 204 beginY2:0, 205 endX1:0, 206 endY1:0, 207 endX2:0, 208 endY2:0 209 }; 210 //--添加裁剪框移动功能 211 Options_move={ 212 beginX1:0, 213 beginY1:0, 214 endX1:0, 215 endY1:0 216 }; 217 218 /** 219 * 拖动裁剪框的逻辑处理。 220 * */ 221 cutBox.addEventListener("touchstart",function(event){ 222 event.preventDefault(); 223 event.stopPropagation(); 224 Options_move={ 225 beginX1:0, 226 beginY1:0, 227 endX1:0, 228 endY1:0 229 }; 230 var beginX=event.changedTouches[0].pageX; 231 var beginY=event.changedTouches[0].pageY; 232 Options_move.beginX1=beginX; 233 Options_move.beginY1=beginY; 234 235 },false); 236 cutBox.addEventListener("touchmove",function(event){ 237 event.preventDefault(); 238 event.stopPropagation(); 239 //-- 240 var beginX=event.changedTouches[0].pageX; 241 var beginY=event.changedTouches[0].pageY; 242 Options_move.endX1=beginX; 243 Options_move.endY1=beginY; 244 //--计算是否发生位移,根据位移来定位裁剪框位置。 245 //位移量。 246 var _d_x=Options_move.endX1-Options_move.beginX1; 247 var _d_y=Options_move.endY1-Options_move.beginY1; 248 //--当前裁剪框原始位置。 249 var _new_x=Options.cutLeft; 250 var _new_y=Options.cutTop; 251 _new_x+=_d_x; 252 _new_y+=_d_y; 253 //--判断是否在矩形边框,假如超出去,那么就取最终点。 254 //--注意,判断相关点的范围。 255 256 if(_new_x<Options.cutBoxLimitX1){ 257 _new_x=Options.cutBoxLimitX1; 258 } 259 else if(_new_x>Options.cutBoxLimitX2){ 260 _new_x=Options.cutBoxLimitX2; 261 } 262 //--顺便判断,加上宽度后,是否超过了范围。 263 if((_new_x+Options.cutViewWidth)>Options.cutBoxLimitX2){ 264 _new_x=Options.cutBoxLimitX2-Options.cutViewWidth; 265 } 266 if(_new_y<Options.cutBoxLimitY1){ 267 _new_y=Options.cutBoxLimitY1; 268 } 269 else if(_new_y>Options.cutBoxLimitY2){ 270 _new_y=Options.cutBoxLimitY2; 271 } 272 //--顺便判断,加上裁剪框高度后,是否超过下限。 273 if((_new_y+Options.cutViewHeight)>Options.cutBoxLimitY2){ 274 _new_y=Options.cutBoxLimitY2-Options.cutViewHeight; 275 } 276 277 278 Options.cutLeft=_new_x; 279 Options.cutTop=_new_y; 280 _resizeCutBox(); 281 //---将这一点的放回前一点。 282 Options_move.beginX1=Options_move.endX1; 283 Options_move.beginY1=Options_move.endY1; 284 285 },false); 286 cutBox.addEventListener("touchend",function(event){ 287 event.preventDefault(); 288 event.stopPropagation(); 289 return; 290 291 },false); 292 /** 293 * 根据相关参数重新resize裁剪框 294 * */ 295 function _resizeCutBox(){ 296 $(cutBox).css({"width":Options.cutViewWidth+"px","height":Options.cutViewHeight+"px","left":Options.cutLeft+"px","top":Options.cutTop+"px"}); 297 } 298 function _getCutImageData(){ 299 var output = document.createElement("canvas"); 300 //--坐标换算。 301 var scale_x=Options_image.width/Options.cropViewWidth; 302 var scale_y=Options_image.height/Options.cropViewHeight; 303 var _o_x=parseInt( (scale_x)*Options.cutLeft); 304 var _o_y=parseInt( (scale_y)*Options.cutTop); 305 //--长度换算 306 var _o_width=parseInt(scale_x*Options.cutViewWidth); 307 var _o_height=parseInt(scale_y*Options.cutViewHeight); 308 309 output.width = Options.cutWidth; 310 output.height = Options.cutHeight; 311 output.getContext("2d").drawImage(img_preview, _o_x,_o_y, _o_width, _o_height, 0, 0, output.width, output.height); 312 return output.toDataURL("image/jpeg"); 313 } 314 function saveImage() 315 { 316 var imgData = _getCutImageData(); 317 /* 318 319 $("#the_show").css("display","block"); 320 321 document.getElementById("showImg").src=imgData; 322 return; 323 */ 324 var xhr = new XMLHttpRequest(); 325 xhr.onreadystatechange = function(e) 326 { 327 if(xhr.readyState == 4) 328 { 329 if(xhr.status == 200) 330 { 331 //--获取返回的数据。 332 var _res=xhr.responseText; 333 _res= $.trim(_res); 334 var json= $.parseJSON(_res); 335 if(json.status==true){ 336 $("#the_show").css("display","block"); 337 var surl=json.url+"?t="+Math.random(); 338 $("#showImg").attr("src",surl); 339 } 340 else{ 341 alert(json.message); 342 } 343 //document.getElementById("status").innerHTML = "<font color='#f00'>上传成功!</font>"; 344 } 345 else{ 346 alert("服务端无法响应,错误编号:"+xhr.status); 347 348 } 349 } 350 }; 351 352 xhr.open("post", "/quickTest/html5CropperHandler.jsp", true); 353 var data = new FormData(); 354 data.append("username", "flashlizi"); 355 data.append("size", 180); 356 data.append("file", imgData); 357 xhr.send(data); 358 } 359 /** 360 * processBar 进度条相关操作。 361 * */ 362 363 Options_process={ 364 beginX:0,//触摸时候起始点 365 beginY:0,//触摸时候起始点 366 endX:0,//触摸时候终点 367 endY:0,//触摸时候终点 368 barWidth:200,//进度条长度 369 pointX:0,//当前指示点位置 370 pointY:0, 371 percent:0,//百分比值。 372 initStatus:false 373 }; 374 var processBar=document.getElementById("processBar"); 375 var processPoint=document.getElementById("processPoint"); 376 377 //--添加触屏事件,监控相关动作。 378 //开始触摸 379 processBar.addEventListener("touchstart",function(event){ 380 event.preventDefault(); 381 event.stopPropagation(); 382 383 if(!Options_process.initStatus){ 384 return; 385 } 386 var beginX=event.changedTouches[0].pageX; 387 var beginY=event.changedTouches[0].pageY; 388 Options_process.beginX=beginX; 389 Options_process.beginY=beginY; 390 },false) ; 391 //--移动中 392 processBar.addEventListener("touchmove",function(event){ 393 event.preventDefault(); 394 event.stopPropagation(); 395 396 if(!Options_process.initStatus){ 397 return; 398 } 399 var beginX=event.changedTouches[0].pageX; 400 var beginY=event.changedTouches[0].pageY; 401 Options_process.endX=beginX; 402 Options_process.endY=beginY; 403 //--计算比分比。 404 var _d_x=Options_process.endX-Options_process.beginX; 405 Options_process.percent+=parseInt(_d_x*100/Options_process.barWidth); 406 if(Options_process.percent<0){ 407 Options_process.percent=0; 408 } 409 else if(Options_process.percent>100){ 410 Options_process.percent=100; 411 } 412 //--计算那个指示点位置。 413 Options_process.pointX=parseInt(Options_process.barWidth*(Options_process.percent/100)); 414 _resizeProcessBar(); 415 //--根据百分比,设置裁剪框大小。 416 var _o_cut_x=Options.cutLeft; 417 var _o_cut_y=Options.cutTop; 418 var _o_cut_width=Options.cutViewWidth; 419 var _new_cut_width= parseInt(Options.cutMaxWidth*(Options_process.percent/100)); 420 var _new_cut_height= parseInt(Options.cutMaxHeight*(Options_process.percent/100)); 421 if(_new_cut_width>_o_cut_width){ 422 //--扩大了。 423 //--计算当前坐标 424 var _d_x_2=_new_cut_width-Options.cutViewWidth; 425 var _d_y_2=_new_cut_height-Options.cutViewHeight; 426 427 Options.cutLeft=Options.cutLeft-parseInt(_d_x_2/2); 428 Options.cutTop=Options.cutTop-parseInt(_d_y_2/2); 429 Options.cutViewWidth=_new_cut_width; 430 Options.cutViewHeight=_new_cut_height; 431 _resizeCutBox(); 432 433 } 434 else if(_new_cut_width<_o_cut_width){ 435 //--缩小了。 436 var _d_x_2=Options.cutViewWidth-_new_cut_width; 437 var _d_y_2=Options.cutViewHeight-_new_cut_height; 438 Options.cutLeft=Options.cutLeft+parseInt(_d_x_2/2); 439 Options.cutTop=Options.cutTop+parseInt(_d_y_2/2); 440 Options.cutViewWidth=_new_cut_width; 441 Options.cutViewHeight=_new_cut_height; 442 _resizeCutBox(); 443 444 } 445 446 //--后续处理。 447 Options_process.beginX=Options_process.endX; 448 Options_process.endY=Options_process.endY; 449 450 },false) ; 451 //--结束 452 processBar.addEventListener("touchend",function(event){ 453 event.preventDefault(); 454 event.stopPropagation(); 455 456 if(!Options_process.initStatus){ 457 return; 458 } 459 },false) ; 460 /** 461 * 根据相关属性,重设slider位置。 462 * */ 463 function _resizeProcessBar(){ 464 $(processPoint).css("left",Options_process.pointX+"px"); 465 } 466 467 468 $("#saveimg").click(function(){ 469 if(Options.initStatus==false){ 470 alert("请先选择图片!"); 471 return; 472 } 473 saveImage(); 474 }); 475 476 </script> 477 478 </body> 479 </html>