摘要:自由拼圖自由拼圖是美圖秀秀中的一個功能,它可以讓用戶在背景圖片上插入自己的圖片,并可以對插入圖片旋轉,拖拽,縮放。效果本屌之前用實現過,參見仿美圖秀秀的自由拼圖注意里面基本上沒代碼說明這里用的實現。
自由拼圖?
自由拼圖是美圖秀秀中的一個功能,它可以讓用戶在背景圖片上插入自己的圖片,并可以對插入圖片旋轉,拖拽,縮放。當然,如果用戶對插入的圖片不滿意,可以用另一張圖片替換選中的圖片,或者刪除選中圖片。
效果本屌之前用actionscript實現過,參見仿美圖秀秀的自由拼圖(注意里面基本上沒代碼說明).
這里用html5的canvas實現。
這個是本屌博客園去年一篇文章的效果,內容也是說的這個,不過很無恥的只貼了代碼,沒有任何說明。
下面的是錄制的效果視頻
http://v.youku.com/v_show/id_XMTM3MTgzMzIyOA==.html
#main_canvas是主要的工作區域
.middleware_img是若干,讀取從外面選中的若干圖片數據,將數據以Base64編碼,依次寫入的src屬性。這些后面將會被當作參數,傳給canvas
#canvas_middleware是代理。讀取的數據由#canvas_middleware調用toDataURL()方法編碼成Base64形式
#puzzle_bg和.middleware_img作用差不多,只不過這里是將背景圖片的src傳給它。它會將背景圖片數據傳給canvas
讀取選中圖片var img_upload_instance=new img_upload({ add_btn:"puzzle_add", onSelect:onSelect//讀取圖片回調 });
define("html5_imgupload",["avalon-min"], function(avalon){ var html5_img_upload=function(options){ this.init(options); } html5_img_upload.prototype = { init : function(options) { //如果有自定義屬性,覆蓋默認屬性 avalon.mix(html5_img_upload.prototype,options); this.init_events(); }, init_events : function() { var _this=this; avalon.bind($(this.add_btn),"change",function(e) { _this.get_files(e); }); }, file_filter:[], ori_images:[], add_btn:null, upload_btn:null, max_upload_num:9, onSelect:function(file_filter){}, _start:0,//已經讀取圖片數量 filter:function(files) { var arrFiles=[]; for (var i=0,file;file=files[i];i++){ if(this._start+i自定義圖片選取回調
...function onSelect(file_filter){ for(var i=this._start,len=file_filter.length;icanvas初始化200||img.height>200){//等比例縮放 var prop=Math.min(200/img.width,200/img.height); img.width=img.width*prop; img.height=img.height*prop; } //設置中轉canvas尺寸 canvas_middleware.width=img.width; canvas_middleware.height=img.height; ctx.drawImage(img, 0, 0, img.width, img.height); //將讀取圖片轉換成base64,寫入.middleware_list的src html5_puzzle.middleware_list.push(canvas_middleware. toDataURL("image/jpeg")); if(!file_filter[i+1]){ //圖片延遲加載到canvas,因為canvas有個讀取過程,但是沒有回調 var t = window.setTimeout(function() { canvas_puzzle.init(); clearTimeout(t); }, 1000); } }; img.src = dataURL; }; delete reader; })(i); reader.readAsDataURL(file_filter[i]);//開始讀取圖片 } this._start=0; img_upload_instance._destroy(); } var canvas = new canvasElement.Element(); canvas.init("main_canvas", { width : canvas_w, height : canvas_h });Canvas.Element.prototype.init = function(el, oConfig) { if (el == "") { return; } this._initElement(el); this._initConfig(oConfig); this._createCanvasBackground(); this._createContainer(); this._initEvents(); this._initCustomEvents(); }; Canvas.Element.prototype._initElement = function(el) { this._oElement = document.getElementById(el); this._oContextTop = this._oElement.getContext("2d"); }; Canvas.Element.prototype._initCustomEvents = function() {//設置自定義事件 this.onRotateStart = new Canvas.CustomEvent("onRotateStart"); this.onRotateMove = new Canvas.CustomEvent("onRotateMove"); this.onRotateComplete = new Canvas.CustomEvent("onRotateComplete"); this.onDragStart = new Canvas.CustomEvent("onDragStart"); this.onDragMove = new Canvas.CustomEvent("onDragMove"); this.onDragComplete = new Canvas.CustomEvent("onDragComplete"); }; Canvas.Element.prototype._initConfig = function(oConfig) { this._oConfig = oConfig; this._oElement.width = this._oConfig.width; this._oElement.height = this._oConfig.height; this._oElement.style.width = this._oConfig.width + "px"; this._oElement.style.height = this._oConfig.height + "px"; }; Canvas.Element.prototype._initEvents = function() { var _this=this; avalon.bind(this._oElement,"mousedown",function(e){ _this.onMouseDown(e); }); avalon.bind(this._oElement,"mouseup",function(e){ _this.onMouseUp(e); }); avalon.bind(this._oElement,"mousemove",function(e){ _this.onMouseMove(e); }); }; Canvas.Element.prototype._createContainer = function() { var canvasEl = document.createElement("canvas"); canvasEl.id = this._oElement.id + "-canvas-container"; ... this._oContextContainer = oContainer.getContext("2d"); }; Canvas.Element.prototype._createCanvasBackground = function() { var canvasEl = document.createElement("canvas"); canvasEl.id = this._oElement.id + "-canvas-background"; ... this._oContextBackground = oBackground.getContext("2d"); };可以看到初始化過程中多次創建
main_canvas-background-canvas繪制背景圖片
main_canvas-container-canvas繪制除當前操作的圖片外的其余圖片
main_canvas繪制當前操作的圖片
從上到下:main_canvas->main_canvas-container-canvas->main_canvas-background-canvas
canvas context:
main_canvas->_oContextTop
main_canvas-container-canvas->_oContextContainer
main_canvas-background-canvas->_oContextBackground
這下就看到canvas自由拼圖的原理了,原來是3個canvas上下重疊起來,操作的時候對不同的canvas進行不同目標的繪制。
canvas繪制圖片接著圖片選取完成后canvas_puzzle.init()
var canvas_img=[]; ... var canvas_puzzle= function() { return { init : function() { var img_list=document.querySelectorAll(".middleware_img"); //第一張作為背景圖片 canvas_img[0]=new canvasImg.Img($("puzzle_bg"), {}); avalon.each(img_list,function(i,el){ canvas_img.push(new canvasImg.Img(el, {})); canvas.addImage(canvas_img[i+1]); }); canvas.setCanvasBackground(canvas_img[0]); ... } }; }();canvasImg.Img是canvas對圖片的封裝.第一個參數是,前面提到的#puzzle_bg和.middleware_img就是作為第一個參數傳入canvasImg.Img.第二個參數用來自定義圖片的一些屬性,比如邊框寬度,4個角的大小等,如果定義的話會覆蓋默認值。
canvasImg.Img封裝圖片后的效果
canvas.addImage(canvas_img[i+1])將canvas對除背景圖片外的圖片的封裝繪制到canvas上
Canvas.Element.prototype.addImage = function(oImg) { if(isEmptyObject(this._aImages)) this._aImages = []; this._aImages.push(oImg); this.renderAll(false,true); };_aImages是保存canvas圖片封裝的數組,renderAll()方法很重要,后面會說到。
canvas.setCanvasBackground(canvas_img[0])將背景圖片繪制到canvas
Canvas.Element.prototype.setCanvasBackground = function(oImg) { this._backgroundImg = oImg; var originalImgSize = oImg.getOriginalSize(); this._oContextBackground.drawImage(oImg._oElement, 0, 0, originalImgSize.width, originalImgSize.height); }; Canvas.Img.prototype.getOriginalSize = function() {//獲得canvas尺寸 return { width: this._oElement.width, height: this._oElement.height } };canvas事件操作 mousedownCanvas.Element.prototype.onMouseDown = function(e) { $("canvas_menu").style.display="none"; var mp = this.findMousePosition(e);//鼠標相對位置 if (this._currentTransform != null || this._aImages == null) { return; } var oImg = this.findTargetImage(mp, false);//獲取目標圖片 //事件觸發位置是不是在4個角上 var action = (!this.findTargetCorner(mp, oImg)) ? "drag" : "rotate"; if (action == "rotate") { this.onRotateMove.fire(e);//觸發自定義事件 } else if (action == "drag") { this.onDragMove.fire(e); } this._prevTransform=this._currentTransform = { target: oImg, action: action, scalex: oImg.scalex, offsetX: mp.ex - oImg.left, offsetY: mp.ey - oImg.top, ex: mp.ex, ey: mp.ey, left: oImg.left, top: oImg.top, theta: oImg.theta }; //設置菜單位置 $("canvas_menu").style.transform="rotate("+oImg.theta*180/3.14+"deg)"; $("canvas_menu").style.left=oImg.left+"px"; $("canvas_menu").style.top=oImg.top+"px"; this.renderAll(false,false); };this._prevTransform保存當前目標圖片狀態,替換圖片時會用到這個變量
renderAll()方法是整個繪制的核心方法
Canvas.Element.prototype.renderAll=function(allOnTop,allowCorners) { var containerCanvas=allOnTop?this._oContextTop:this._oContextContainer; this._oContextTop.clearRect(0,0,parseInt(this._oConfig.width),parseInt(this._oConfig.height)); containerCanvas.clearRect(0,0,parseInt(this._oConfig.width),parseInt(this._oConfig.height)); if(allOnTop){//所有圖片都要在最上面 var originalImgSize=this._backgroundImg.getOriginalSize(); //在最上層canvas繪制背景圖片 this._oContextTop.drawImage(this._backgroundImg._oElement, 0, 0, originalImgSize.width,originalImgSize.height); } for(var i=0,l=this._aImages.length-1;i可以看到,如果allOnTop=false,從_aImages封裝圖片的數組的第一個元素到倒數第二個元素,會繪制到中間一層container-canvas,而_aImages數組的最后一個元素,即當前操作的圖片,會繪制到最上面一層top-canvas,當然如果改變操作對象,_aImages數組也會相應的變化,保證當前操作的圖片在_aImages數組的最后一個位置。
如果allOnTop=true,_aImages數組中的所有圖片還有背景圖片都會被繪制到最上面一層top-canvas.
設置allOnTop參數的目的在于上傳時只有所有圖片都在一個canvas context上,調用toDataURL()方法,就能獲得整個拼圖的base64字符串。
第二個參數allowCorners表示繪制時是否添加邊框,邊角。前面將選中圖片及背景圖片繪制到canvas,最后this.renderAll(false,true)讓邊框,邊角可見,是為了讓用戶知道圖片可以進行操作。
Canvas.Element.prototype.addImage = function(oImg) { ... this.renderAll(false,true); };另外,無論怎么設置,renderAll()方法最終都會調用drawImageElement()方法進行實際意義上的繪制
Canvas.Element.prototype.drawImageElement = function(context,oImg,allowCorners) { if(oImg){ oImg.cornervisibility=allowCorners; var offsetY = oImg.height / 2; var offsetX = oImg.width / 2; context.save(); context.translate(oImg.left, oImg.top); context.rotate(oImg.theta); context.scale(oImg.scalex, oImg.scaley); this.drawBorder(context, oImg, offsetX, offsetY); var originalImgSize = oImg.getOriginalSize(); var polaroidHeight =((oImg.height-originalImgSize.height)-(oImg.width-originalImgSize.width))/2; context.drawImage(oImg._oElement,-originalImgSize.width/2,(-originalImgSize.height)/2-polaroidHeight, originalImgSize.width,originalImgSize.height); if (allowCorners) this.drawCorners(context, oImg, offsetX, offsetY); context.restore(); } };mousemoveCanvas.Element.prototype.onMouseMove = function(e) { var mp = this.findMousePosition(e); if(this._aImages == null) return; if(this._currentTransform==null){ var targetImg = this.findTargetImage(mp, true); this.setCursor(mp, targetImg); } else { if (this._currentTransform.action == "rotate") { this.rotateImage(mp); this.scaleImage(mp); this.onRotateMove.fire(e); } else { this.translateImage(mp); this.onDragMove.fire(e); } this.renderTop(); } };里面的renderTop()方法只在最上層canvas繪制當前操作的圖片
Canvas.Element.prototype.renderTop = function() { this._oContextTop.clearRect(0,0,parseInt(this._oConfig.width), parseInt(this._oConfig.height)); this.drawImageElement(this._oContextTop, this._aImages[this._aImages.length-1],true); };mouseupCanvas.Element.prototype.onMouseUp = function(e) { if (this._aImages == null) { return; } var target=this._currentTransform.target; if (target) target.setImageCoords();//重置圖片canvas封裝 if(this._currentTransform!= null&&this._currentTransform.action=="rotate") { this.onRotateComplete.fire(e); } else if (this._currentTransform!=null&&this._currentTransform.action == "drag"){ this.onDragComplete.fire(e); } this._currentTransform = null; this.renderTop(); if(this._aImages.length>0)//沒有選中的圖片 $("canvas_menu").style.display="block"; };替換圖片avalon.bind($("puzzle_update"),"click",function(){ update_puzzle=true; $("puzzle_add_input").click(); });可以看到,這里依然使用了點擊選中圖片,不過設置了update_puzzle=true,表示當前處在替換圖片的情況下
function onSelect(file_filter){ for(var i=this._start,len=file_filter.length;i刪除圖片 avalon.bind($("puzzle_delete"),"click",function(){ canvas._aImages.splice(getCurImg(),1);//從_aImages數組中刪除 canvas.renderAll(false,false);//重新繪制 $("canvas_menu").style.display="none"; ... });拼圖轉換成base64字符串Canvas.Element.prototype.canvasTo = function(format) {//canvas=>dataurl this.renderAll(true,false);//所有圖片都繪制到最上層,并且不繪制邊框,邊角 if (format == "jpeg" || format == "png") { return this._oElement.toDataURL("image/"+format); } };上傳avalon.post("...",{ imgData:canvas.canvasTo("jpeg").substr(22) },function(data){ ... },"json");后臺用Base64解析imgData字符串就可以了
下載
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/86121.html
摘要:繪制圖片文字按比例計算不同手機的百分比間距將閉包引用清除,釋放內存不支持 最近做項目的時候遇到照片拼圖的功能,便在這里分享自己的封裝的canvas拼圖功能,可能代碼寫的不好,如果有疑問或者是有更好的方法的,可以私聊我,或者是評論指出,感謝各位 實現的思路其實挺簡單的,主要是通過服務端獲取圖片鏈接,圖片寬度,圖片高度,然后利用簡單的遞歸實現就行了(注意移動端需要采用2倍數的比例,否則會...
摘要:繪制圖片文字按比例計算不同手機的百分比間距將閉包引用清除,釋放內存不支持 最近做項目的時候遇到照片拼圖的功能,便在這里分享自己的封裝的canvas拼圖功能,可能代碼寫的不好,如果有疑問或者是有更好的方法的,可以私聊我,或者是評論指出,感謝各位 實現的思路其實挺簡單的,主要是通過服務端獲取圖片鏈接,圖片寬度,圖片高度,然后利用簡單的遞歸實現就行了(注意移動端需要采用2倍數的比例,否則會...
摘要:繪制圖片文字按比例計算不同手機的百分比間距將閉包引用清除,釋放內存不支持 最近做項目的時候遇到照片拼圖的功能,便在這里分享自己的封裝的canvas拼圖功能,可能代碼寫的不好,如果有疑問或者是有更好的方法的,可以私聊我,或者是評論指出,感謝各位 實現的思路其實挺簡單的,主要是通過服務端獲取圖片鏈接,圖片寬度,圖片高度,然后利用簡單的遞歸實現就行了(注意移動端需要采用2倍數的比例,否則會...
摘要:而微信小程序中也剛好有進度條這個組件。推薦和意見反饋推薦給朋友意見反饋這個兩個功能就是用了,微信小程序的組件,這里需要注意的就是,在清除的默認樣式時,需要把的偽元素的邊框也去掉。總結這次做的這個九宮格心形拼圖的小程序,第一版已經上線了。 說明 前幾天在朋友圈看到好幾次這種圖片。 showImg(https://segmentfault.com/img/bVbeAoX?w=321&h=3...
閱讀 2841·2021-11-25 09:43
閱讀 2488·2021-10-09 09:44
閱讀 2805·2021-09-22 15:49
閱讀 2578·2021-09-01 11:43
閱讀 2548·2019-08-30 14:16
閱讀 469·2019-08-29 17:24
閱讀 3026·2019-08-29 14:00
閱讀 1389·2019-08-29 13:05