成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

canvas自由拼圖

megatron / 1528人閱讀

摘要:自由拼圖自由拼圖是美圖秀秀中的一個(gè)功能,它可以讓用戶在背景圖片上插入自己的圖片,并可以對(duì)插入圖片旋轉(zhuǎn),拖拽,縮放。效果本屌之前用實(shí)現(xiàn)過,參見仿美圖秀秀的自由拼圖注意里面基本上沒代碼說明這里用的實(shí)現(xiàn)。

自由拼圖?

自由拼圖是美圖秀秀中的一個(gè)功能,它可以讓用戶在背景圖片上插入自己的圖片,并可以對(duì)插入圖片旋轉(zhuǎn),拖拽,縮放。當(dāng)然,如果用戶對(duì)插入的圖片不滿意,可以用另一張圖片替換選中的圖片,或者刪除選中圖片。

效果

本屌之前用actionscript實(shí)現(xiàn)過,參見仿美圖秀秀的自由拼圖(注意里面基本上沒代碼說明).
這里用html5的canvas實(shí)現(xiàn)。

這個(gè)是本屌博客園去年一篇文章的效果,內(nèi)容也是說的這個(gè),不過很無恥的只貼了代碼,沒有任何說明。
下面的是錄制的效果視頻
http://v.youku.com/v_show/id_XMTM3MTgzMzIyOA==.html

布局

#main_canvas是主要的工作區(qū)域

.middleware_img是若干,讀取從外面選中的若干圖片數(shù)據(jù),將數(shù)據(jù)以Base64編碼,依次寫入的src屬性。這些后面將會(huì)被當(dāng)作參數(shù),傳給canvas

#canvas_middleware是代理。讀取的數(shù)據(jù)由#canvas_middleware調(diào)用toDataURL()方法編碼成Base64形式

#puzzle_bg.middleware_img作用差不多,只不過這里是將背景圖片的src傳給它。它會(huì)將背景圖片數(shù)據(jù)傳給canvas

讀取選中圖片
    var img_upload_instance=new img_upload({
        add_btn:"puzzle_add",
        onSelect:onSelect//讀取圖片回調(diào)
    });
define("html5_imgupload",["avalon-min"], function(avalon){
    var html5_img_upload=function(options){
        this.init(options);
    }
    html5_img_upload.prototype = {
        init : function(options) {
            //如果有自定義屬性,覆蓋默認(rèn)屬性
            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,//已經(jīng)讀取圖片數(shù)量
        filter:function(files) {
            var arrFiles=[];
            for (var i=0,file;file=files[i];i++){
                if(this._start+i

自定義圖片選取回調(diào)

...
    function onSelect(file_filter){
        for(var i=this._start,len=file_filter.length;i200||img.height>200){//等比例縮放
                            var prop=Math.min(200/img.width,200/img.height);
                            img.width=img.width*prop;
                            img.height=img.height*prop;
                        }
                        //設(shè)置中轉(zhuǎn)canvas尺寸
                        canvas_middleware.width=img.width;
                        canvas_middleware.height=img.height;
                        ctx.drawImage(img, 0, 0, img.width, img.height);
                        //將讀取圖片轉(zhuǎn)換成base64,寫入.middleware_list的src
                        html5_puzzle.middleware_list.push(canvas_middleware.
                        toDataURL("image/jpeg"));
                        if(!file_filter[i+1]){
                            //圖片延遲加載到canvas,因?yàn)閏anvas有個(gè)讀取過程,但是沒有回調(diào)
                            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();
    }
canvas初始化
    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() {//設(shè)置自定義事件
        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"); 
    };

可以看到初始化過程中多次創(chuàng)建

main_canvas-background-canvas繪制背景圖片

main_canvas-container-canvas繪制除當(dāng)前操作的圖片外的其余圖片

main_canvas繪制當(dāng)前操作的圖片

從上到下: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個(gè)canvas上下重疊起來,操作的時(shí)候?qū)Σ煌腸anvas進(jìn)行不同目標(biāo)的繪制。

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對(duì)圖片的封裝.第一個(gè)參數(shù)是,前面提到的#puzzle_bg和.middleware_img就是作為第一個(gè)參數(shù)傳入canvasImg.Img.第二個(gè)參數(shù)用來自定義圖片的一些屬性,比如邊框?qū)挾龋?個(gè)角的大小等,如果定義的話會(huì)覆蓋默認(rèn)值。

canvasImg.Img封裝圖片后的效果

canvas.addImage(canvas_img[i+1])將canvas對(duì)除背景圖片外的圖片的封裝繪制到canvas上

    Canvas.Element.prototype.addImage = function(oImg) {
        if(isEmptyObject(this._aImages))
            this._aImages = [];
        this._aImages.push(oImg);
        this.renderAll(false,true);
    };

_aImages是保存canvas圖片封裝的數(shù)組,renderAll()方法很重要,后面會(huì)說到。

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事件操作 mousedown
    Canvas.Element.prototype.onMouseDown = function(e) {
        $("canvas_menu").style.display="none";
        var mp = this.findMousePosition(e);//鼠標(biāo)相對(duì)位置
        if (this._currentTransform != null || this._aImages == null) {
            return;
        }
        var oImg = this.findTargetImage(mp, false);//獲取目標(biāo)圖片
        //事件觸發(fā)位置是不是在4個(gè)角上
        var action = (!this.findTargetCorner(mp, oImg)) ? "drag" : "rotate";
        if (action == "rotate") {
            this.onRotateMove.fire(e);//觸發(fā)自定義事件
        } 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 
        };
        //設(shè)置菜單位置
        $("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保存當(dāng)前目標(biāo)圖片狀態(tài),替換圖片時(shí)會(huì)用到這個(gè)變量

renderAll()方法是整個(gè)繪制的核心方法

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封裝圖片的數(shù)組的第一個(gè)元素到倒數(shù)第二個(gè)元素,會(huì)繪制到中間一層container-canvas,而_aImages數(shù)組的最后一個(gè)元素,即當(dāng)前操作的圖片,會(huì)繪制到最上面一層top-canvas,當(dāng)然如果改變操作對(duì)象,_aImages數(shù)組也會(huì)相應(yīng)的變化,保證當(dāng)前操作的圖片在_aImages數(shù)組的最后一個(gè)位置。
如果allOnTop=true,_aImages數(shù)組中的所有圖片還有背景圖片都會(huì)被繪制到最上面一層top-canvas.
設(shè)置allOnTop參數(shù)的目的在于上傳時(shí)只有所有圖片都在一個(gè)canvas context上,調(diào)用toDataURL()方法,就能獲得整個(gè)拼圖的base64字符串。
第二個(gè)參數(shù)allowCorners表示繪制時(shí)是否添加邊框,邊角。

前面將選中圖片及背景圖片繪制到canvas,最后this.renderAll(false,true)讓邊框,邊角可見,是為了讓用戶知道圖片可以進(jìn)行操作。

    Canvas.Element.prototype.addImage = function(oImg) {
        ...
        this.renderAll(false,true);
    };

另外,無論怎么設(shè)置,renderAll()方法最終都會(huì)調(diào)用drawImageElement()方法進(jìn)行實(shí)際意義上的繪制

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();
    }
};
mousemove
Canvas.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繪制當(dāng)前操作的圖片

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);
};
mouseup
    Canvas.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();
    });

可以看到,這里依然使用了點(diǎn)擊選中圖片,不過設(shè)置了update_puzzle=true,表示當(dāng)前處在替換圖片的情況下

    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數(shù)組中刪除
        canvas.renderAll(false,false);//重新繪制
        $("canvas_menu").style.display="none";
        ...
    });
拼圖轉(zhuǎn)換成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");

后臺(tái)用Base64解析imgData字符串就可以了

下載

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/86121.html

相關(guān)文章

  • canvas拼圖功能實(shí)現(xiàn)

    摘要:繪制圖片文字按比例計(jì)算不同手機(jī)的百分比間距將閉包引用清除,釋放內(nèi)存不支持 最近做項(xiàng)目的時(shí)候遇到照片拼圖的功能,便在這里分享自己的封裝的canvas拼圖功能,可能代碼寫的不好,如果有疑問或者是有更好的方法的,可以私聊我,或者是評(píng)論指出,感謝各位 實(shí)現(xiàn)的思路其實(shí)挺簡(jiǎn)單的,主要是通過服務(wù)端獲取圖片鏈接,圖片寬度,圖片高度,然后利用簡(jiǎn)單的遞歸實(shí)現(xiàn)就行了(注意移動(dòng)端需要采用2倍數(shù)的比例,否則會(huì)...

    付倫 評(píng)論0 收藏0
  • canvas拼圖功能實(shí)現(xiàn)

    摘要:繪制圖片文字按比例計(jì)算不同手機(jī)的百分比間距將閉包引用清除,釋放內(nèi)存不支持 最近做項(xiàng)目的時(shí)候遇到照片拼圖的功能,便在這里分享自己的封裝的canvas拼圖功能,可能代碼寫的不好,如果有疑問或者是有更好的方法的,可以私聊我,或者是評(píng)論指出,感謝各位 實(shí)現(xiàn)的思路其實(shí)挺簡(jiǎn)單的,主要是通過服務(wù)端獲取圖片鏈接,圖片寬度,圖片高度,然后利用簡(jiǎn)單的遞歸實(shí)現(xiàn)就行了(注意移動(dòng)端需要采用2倍數(shù)的比例,否則會(huì)...

    Kyxy 評(píng)論0 收藏0
  • canvas拼圖功能實(shí)現(xiàn)

    摘要:繪制圖片文字按比例計(jì)算不同手機(jī)的百分比間距將閉包引用清除,釋放內(nèi)存不支持 最近做項(xiàng)目的時(shí)候遇到照片拼圖的功能,便在這里分享自己的封裝的canvas拼圖功能,可能代碼寫的不好,如果有疑問或者是有更好的方法的,可以私聊我,或者是評(píng)論指出,感謝各位 實(shí)現(xiàn)的思路其實(shí)挺簡(jiǎn)單的,主要是通過服務(wù)端獲取圖片鏈接,圖片寬度,圖片高度,然后利用簡(jiǎn)單的遞歸實(shí)現(xiàn)就行了(注意移動(dòng)端需要采用2倍數(shù)的比例,否則會(huì)...

    entner 評(píng)論0 收藏0
  • 小程序—九宮格心形拼圖

    摘要:而微信小程序中也剛好有進(jìn)度條這個(gè)組件。推薦和意見反饋推薦給朋友意見反饋這個(gè)兩個(gè)功能就是用了,微信小程序的組件,這里需要注意的就是,在清除的默認(rèn)樣式時(shí),需要把的偽元素的邊框也去掉??偨Y(jié)這次做的這個(gè)九宮格心形拼圖的小程序,第一版已經(jīng)上線了。 說明 前幾天在朋友圈看到好幾次這種圖片。 showImg(https://segmentfault.com/img/bVbeAoX?w=321&h=3...

    myeveryheart 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<