在触屏设备上面利用html5裁剪图片

前言

现在触屏设备越来越流行,而且大多数已经支持html5了。针对此,对触屏设备开发图片裁剪功能,

让其可以直接处理图片,减轻服务端压力。

技术点

浏览器必须支持html5,包括fileReader,canvas等api,并且该设备至少支持单点触事件(touchstart,touchmove,touchend),可惜的是

很多浏览器只能识别一只手指(不支持多点触摸事件,假如支持的话,请告知我)。

思路

利用filereader直接读取本地图片,然后赋予一个图片,该图片及裁剪框的位置计算跟pc端一样,但是触发的事件不一样,触屏版是根据触屏事件触发的。裁剪时,利用cavas的api直接画出相关图像,然后得到数据,再利用xmlhttprequest发送请求。

非html5无法完成这个过程。

运行结果

这只是一个demo,也是最初的雏形,当然不会太好看了,但是基本实现功能即可。
 
在触屏设备上面利用html5裁剪图片
 

部分代码

  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>

 

你可能感兴趣的:(html5)