html5图片裁剪控件原型【含缩放,旋转,拖动功能】---2、核心代码

推荐

这一篇文章是早年为了解决图片裁剪的探索性文章,现在已经开放出了falsh版及html5版本的图片裁剪插件,各位有时间可以看看:
浮士德html5图片裁剪器2016开源版
浮士德头像裁剪flash版2016福利版
上面两个解决方案已经经过多个项目的成功应用,适用于低级浏览器及现代浏览器,ipad,android,iphone4s,iphone5,iphone5s,iphone6等设备等。

前言

将相应html及js脚本放出来,注意,这是原型,并非立刻可以使用的东西。

html界面

<!DOCTYPE HTML>
<html>
<head>
    <title>个人相片</title>
    <meta charset="utf-8" />
    <meta content="width=device-width, initial-scale=1,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport"/>
    <meta content="yes" name="apple-mobile-web-app-capable">
    <meta content="black" name="apple-mobile-web-app-status-bar-style">
    <meta content="telephone=yes" name="format-detection">
    <meta content="email=no" name="format-detection">
    <script type="text/javascript" src="/static/lib/jquery-1.11.0.min.js"></script>
    <script type="text/javascript" src="/static/lib/util.min.js"></script>
    <script type="text/javascript" src="/static/lib/exif.min.js"></script>
    <script type="text/javascript" src="/static/vendor/ImgTools/ImageOrientationFix.js"></script>
    <script type="text/javascript" src="/static/vendor/ImgTools/ImageResizer.js"></script>
    <script type="text/javascript" src="/static/vendor/layer1.8/layer/layer.min.js"></script>
    <link href="/static/vendor/jQuery.showLoading/css/showLoading.css" rel="stylesheet" media="screen" />
    <script type="text/javascript" src="/static/vendor/jQuery.showLoading/js/jquery.showLoading.js"></script>
    <script type="text/javascript" src="ImageEditor.js"></script>
</head>
<body>

<div>
    <h3>请选择图片:</h3>
    <div>
        <input type="file" style="" id="uploadImage"  accept="image/*">
    </div>
</div>


<h2>裁剪器区域</h2>
<div id="div_cropper">
        <div ui="image-editor-panel">
            <canvas ui="background-layout" style="position: absolute; "></canvas>
            <canvas ui="cutter-layout" style="position: absolute; "></canvas>
            <canvas ui="operation-layout" style="position: absolute; cursor: pointer; "></canvas>
        </div>
</div>
<div class="image-editor-tool-bar">
<button type="button" id="btn_zoom_in">放大图片</button>
<button type="button" id="btn_zoom_out">缩小图片</button>
<button type="button" id="btn_turn_left">向左转</button>
<button type="button" id="btn_turn_right">向右转</button>
</div>

<div><input type="button" id="btn_save" value="保存图片"></div>
<h2>预览结果:</h2>
<img src="" id="preview_img"/>
    <div id="tips" style="color:green;"></div>

<script type="text/javascript" src="example2.js"></script>
<div id="debug" style="color: green;">

</div>
<img src="daojian_1.png" id="img_test" style="display: none;">
<div  style="width:1200px;height: 800px; border: 1px solid red;display: none;">
    <canvas id="test_canvas" width="1200" height="800"></canvas>
</div>
<script type="text/javascript">
    var _img=document.getElementById("img_test");
    var _canvas=document.getElementById("test_canvas");
    var _context=_canvas.getContext("2d");

</script>
</body>

</html>

核心组件代码

/**
 * 图片裁剪组件,必须跟jquery exif ImageOrientationFix.js 等一起用。
 */
function ImageEditor(opts){

    var settings={
        cutWidth:150 //裁剪框的宽度。
        ,cutHeight:200 //裁剪框的高度
        ,containerWidth:800 //容器宽度
        ,containerHeight:600 //容器高度
        ,imageShowWidth:400 //图片默认显示的宽度【会按照要求的宽度及高度等比缩放】
        ,imageShowHeight:500 //图片默认显示的高度【会按照要求的宽度及高度等比缩放】
        ,containerElement:"" //容器的默认元素,jquery元素或者原生dom元素。
        ,showCutterInFirst:true //是否第一时间显示裁剪框---注意,true意味着即使用户没有选择图片,背景层没有显示图片裁剪框也显示。
        ,saveMode:"ratio" //裁剪图片的模式有两种,一种是 ratio即,在原图片上面按照比例裁剪图片,尺寸不一定固定,一种是 size,严格按照裁剪框的尺寸来保存图片。

    };
    $.extend(settings,opts);
    function _debug(str){
        return;
        var _div=$("<div></div>");
        _div.text(str);
        $("#debug").append(_div);
    };

    function _clearDebug(){
        return;
        $("#debug").empty();
    }
    var __me=this;

    //--定义必须用到的dom元素的句柄。
    var containerElement=$(settings.containerElement);//整个容器对象,
    var panel=containerElement.find('[ui="image-editor-panel"]');//这是包装几个画布的面板。
    var canvas_bg=containerElement.find('[ui="background-layout"]')[0];//原生画布对象,这是背景画布,用于绘制图片及缩放,转向等操作。
    var canvas_cutter=containerElement.find("[ui='cutter-layout']")[0];//原生画布对象,这是裁剪框对象。用于绘制遮罩层及裁剪框。
    var canvas_operation=containerElement.find("[ui='operation-layout']")[0];//原生画布对象,这是操作层对象,用于接受用户的鼠标及手势,然后触发各种事件。

    var context_cutter=canvas_cutter.getContext("2d");
    var context_bg=canvas_bg.getContext("2d");

    var data_origin_image="";//原始图片数据,原始是指经过了ios bug修复剂orientation方向修复以后的base64格式的数据。注意,必要时,假如图片太大,可以先压缩到某个尺寸。
    var image_background=new Image();//新建一个背景图的元素用于处理图片旋转等操作。
    //--下面预先将背景图片的三个方向都先计算出来。

    //window.image_backgroud=image_background;


    //--内部运行时数据
    var data_cutter={
        location:{
            x:0
            ,y:0
        }
        ,size:{
            w:0
            ,h:0
        }
        ,ratio:1
    };//裁剪框数据
    var data_bg={
        angle:0
        ,img_bg_0:""//0度图片。
        ,img_bg_90:""//90度图片
        ,img_bg_180:""//180度旋转图片
        ,img_bg_270:""//270度旋转图片
        ,location:{
            x:0
            ,y:0
        }
        ,size:{
            w:0
            ,h:0
        }
        ,originSize:{
            w:0
            ,h:0
        }
        ,zoom:1
    };//背景层数据-这个作为原始备份,告诉系统应该如何转换数据。

    var data_bg_trans={
        angle:0
        ,location:{
            x:0
            ,y:0
        }
        ,size:{
            w:0
            ,h:0
        }
        ,originSize:{
            w:0
            ,h:0
        }
        ,zoom:1
    };//在非正常情况下的位置的数据等,例如,转换成为90,180,270,0等的数据,


    var data_container={
        size:{
            w:0
            ,h:0
        }

    };//容器数据

    var data_op={
        panelInfo:{
            absoluteTop:0,
            absoluteLeft:0,
            offsetWidth:0,
            offsetHeight:0
        }
        ,isInDrag:false
        ,beginPoint:{
            x:0
            ,y:0
        }
        ,point1:{
            x:0
            ,y:0
        }
        ,point2:{
            x:0
            ,y:0
        }
        //判断是否在裁剪框里面。
        ,isInCut:false
    };//操控层数据。
    var appData={
        panelSize:{
            w:0
            ,h:0
        }
        ,hasImage:false //是否已经选择了图片,容器里面有没有图片。
    };//app内部数据

    var innerTools={
        browser:{
        versions:function(){
            var u = navigator.userAgent, app = navigator.appVersion;
            return {         //移动终端浏览器版本信息
                trident: u.indexOf('Trident') > -1, //IE内核
                presto: u.indexOf('Presto') > -1, //opera内核
                webKit: u.indexOf('AppleWebKit') > -1, //苹果、谷歌内核
                gecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') == -1, //火狐内核
                mobile: !!u.match(/AppleWebKit.*Mobile.*/), //是否为移动终端
                ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), //ios终端
                android: u.indexOf('Android') > -1 || u.indexOf('Linux') > -1, //android终端或uc浏览器
                iPhone: u.indexOf('iPhone') > -1 , //是否为iPhone或者QQHD浏览器
                iPad: u.indexOf('iPad') > -1, //是否iPad
                webApp: u.indexOf('Safari') == -1 //是否web应该程序,没有头部与底部
            };
        }(),
        language:(navigator.browserLanguage || navigator.language).toLowerCase()
        }
        ,GetAbsoluteLocationEx:function(element){
        if(arguments.length!=1||element==null)
        {
            return null;
        }
        var elmt=element;
        var offsetTop=elmt.offsetTop;
        var offsetLeft=elmt.offsetLeft;
        var offsetWidth=elmt.offsetWidth;
        var offsetHeight=elmt.offsetHeight;
        while(elmt=elmt.offsetParent)
        {
            // add this judge
            if(elmt.style.position=='absolute'||elmt.style.position=='relative'
                ||(elmt.style.overflow!='visible'&&elmt.style.overflow!=''))
            {
                break;
            }
            offsetTop+=elmt.offsetTop;
            offsetLeft+=elmt.offsetLeft;
        }
        var _info={absoluteTop:offsetTop,absoluteLeft:offsetLeft,
            offsetWidth:offsetWidth,offsetHeight:offsetHeight};

            return _info;

    }
    };



    var app={
        init:function(){
            var _child=this;
            appData.panelSize.w=settings.containerWidth;
            appData.panelSize.h=settings.containerHeight;


            _child.render_ui();
            _child.init_cutter();
            _child.initOperationLayout();
        }
        //--设定容器及其他相关dom的样式。
      ,render_ui:function(){
      containerElement.css({"width":settings.containerWidth+"px","height":settings.containerHeight+"px"});
      canvas_bg.width=settings.containerWidth;
      canvas_bg.height=settings.containerHeight;

      canvas_cutter.width=settings.containerWidth;
      canvas_cutter.height=settings.containerHeight;

      canvas_operation.width=settings.containerWidth;
      canvas_operation.height=settings.containerHeight;

      }
        //--计算相关数据--初始化裁剪框。
        ,init_cutter:function(){
            var _child=this;
            data_cutter.size.w=settings.cutWidth;
            data_cutter.size.h=settings.cutHeight;
            data_cutter.ratio=settings.cutWidth/settings.cutHeight;

            //--计算中间点。
            var middle_x=appData.panelSize.w/2;
            var middle_y=appData.panelSize.h/2;
            var _x=middle_x-data_cutter.size.w/2;
            var _y=middle_y-data_cutter.size.h/2;
            data_cutter.location.x=_x;
            data_cutter.location.y=_y;
            if(settings.showCutterInFirst){
            _child.updateCutter();
            }

        }
        //--根据cutter的数据更新裁剪框层。
        ,updateCutter:function(){
            var _child=this;
            console.log("目前更新裁剪框,数据"+data_cutter.location.x+":x "+data_cutter.location.y);
            _child.__clear_cutter_canvas();
            _child.__draw_mask();
            _child.__draw_cutter();

        }
        //--清空cutter画布的内容。
        ,__clear_cutter_canvas:function(){
            context_cutter.clearRect(0,0,canvas_cutter.width,canvas_cutter.height);
        }
        //--渲染cutter
        ,__draw_cutter:function(){
           //
            context_cutter.strokeStyle = '#ffffff';
            context_cutter.lineWidth = 2;
            context_cutter.strokeRect(data_cutter.location.x,data_cutter.location.y,data_cutter.size.w,data_cutter.size.h);
            context_cutter.clearRect(data_cutter.location.x,data_cutter.location.y,data_cutter.size.w,data_cutter.size.h);
            //context_cutter.clearRect(0,0,canvas_cutter.width,canvas_cutter.height);
        }
       //--渲染遮罩层
        ,__draw_mask:function(){
            context_cutter.fillStyle = 'rgba(0, 0, 0, 0.3)';
            context_cutter.fillRect(0, 0, canvas_cutter.width,canvas_cutter.height);
        }

        //--对外接口,拖动裁剪框。
        ,MoveCutter:function(pos_x,pos_y){
            var _child=this;
            data_cutter.location.x=pos_x;
            data_cutter.location.y=pos_y;
            _child.updateCutter();
        }
        //--移动裁剪框。
        ,MoveCutterByDelta:function(delta_x,delta_y){
            var _child=this;
            var _new_x= data_cutter.location.x+delta_x;
            var _new_y=data_cutter.location.y+delta_y;
            //--判断边界。
            if(_new_x<0){
                _new_x=0;
            }
            if(_new_y<0){
                _new_y=0;
            }
            if(_new_x>(appData.panelSize.w-data_cutter.size.w)){
                _new_x=appData.panelSize.w-data_cutter.size.w;
            }
            if(_new_y>(appData.panelSize.h-data_cutter.size.h)){
                _new_y=appData.panelSize.h-data_cutter.size.h;
            }
            console.log("新的裁剪框位置是:"+_new_x+" X "+_new_y);
            _child.MoveCutter(_new_x,_new_y);
        }

        //--初始化背景层。背景层需要获得图片的base64字符串先。
        ,init_background:function(base_64_image){
          var _child=this;
            _child.init_bg_data(base_64_image,function(finalBase64Image){

                _child.updateBackground();

            });
        }
        //--背景图片等图片处理。数据处理等。
        ,init_bg_data:function(originBase64,callback){
          ImageOrientationFix({
            image:originBase64
            ,imgType:"base64"
            ,onFix:function(base64_str){
               data_origin_image=base64_str;
               image_background.onload=function(){
                   appData.hasImage=true;
                   //--获取相关数据及计算。
                    data_bg.originSize.w=image_background.naturalWidth;
                    data_bg.originSize.h=image_background.naturalHeight;
                    data_bg.angle=0;
                    data_bg.location.x=0;
                    data_bg.location.y=0;
                    data_bg.zoom=1;
                    data_bg.size.w=data_bg.originSize.w;
                    data_bg.size.h=data_bg.originSize.h;
                    image_background.onload=function(){};
                    console.log("bg层数据");
                    console.log(data_bg);
                   //--按照当前显示区域,尽量显示全部图片,计算显示尺寸。
                   if(data_bg.originSize.w>appData.panelSize.w||data_bg.originSize.h>appData.panelSize.h){
                       var _max_scale= Math.max(data_bg.originSize.w/appData.panelSize.w,data_bg.originSize.h/appData.panelSize.h);
                       var _current_w=data_bg.originSize.w/_max_scale;
                       var _current_h=data_bg.originSize.h/_max_scale;
                       data_bg.zoom=1/_max_scale;
                       data_bg.size.w=_current_w;
                       data_bg.size.h=_current_h;
                       console.log("bg层计算得到的可以显示区域是:");
                       console.log(data_bg);
                   }
                   //--计算背景层应该显示的位置,尽量放到中间。
                   data_bg.location.x=(appData.panelSize.w-data_bg.size.w)/2;
                   data_bg.location.y=(appData.panelSize.h-data_bg.size.h)/2;
                   //--计算各个状体的图片。
                   data_bg.img_bg_0=image_background;
                   data_bg.img_bg_90=new Image();
                    data_bg.img_bg_180=new Image();
                    data_bg.img_bg_270=new Image();

                   var __canvas=document.createElement("canvas");
                   var __context=__canvas.getContext("2d");
                   //$(document.body).prepend(__canvas);
                   //--90度的。
                   __canvas.width=data_bg.originSize.h;
                   __canvas.height=data_bg.originSize.w;
                   __context.save();
                   __context.rotate(Math.PI/2);
                   __context.drawImage(image_background,0,0-data_bg.originSize.h);
                   __context.restore();
                   data_bg.img_bg_90.src= __canvas.toDataURL();
                   __context.clearRect(0,0,__canvas.width,__canvas.height);
                   //--180度的。
                   __canvas.width=data_bg.originSize.w;
                   __canvas.height=data_bg.originSize.h;
                   __context.save();
                   __context.rotate(Math.PI);
                   __context.drawImage(image_background,0-data_bg.originSize.w,0-data_bg.originSize.h);
                   __context.restore();
                   data_bg.img_bg_180.src= __canvas.toDataURL();
                   __context.clearRect(0,0,__canvas.width,__canvas.height);

                   //--270度的。
                   __canvas.width=data_bg.originSize.h;
                   __canvas.height=data_bg.originSize.w;
                   __context.save();
                   __context.rotate(Math.PI*1.5);
                   __context.drawImage(image_background,0-data_bg.originSize.w,0);
                   __context.restore();
                   data_bg.img_bg_270.src= __canvas.toDataURL();
                   __context.clearRect(0,0,__canvas.width,__canvas.height);
                   //--trans 背景数据。
                   data_bg_trans.angle=data_bg.angle;

                   data_bg_trans.location.x=data_bg.location.x;
                   data_bg_trans.location.y=data_bg.location.y;

                   data_bg_trans.size.w=data_bg.size.w;
                   data_bg_trans.size.h=data_bg.size.h;

                   data_bg_trans.originSize.w=data_bg.originSize.w;
                   data_bg_trans.originSize.h=data_bg.originSize.h;

                   data_bg_trans.zoom=data_bg.zoom;





                   if(callback){
                       callback(base64_str);
                   }

               };
                  image_background.src=base64_str;
            }
        });
        }

        //--清空背景。
        ,clear_background:function(){
            context_bg.clearRect(0,0,canvas_bg.width,canvas_bg.height);
        }
        //--根据相关数据更新背景层图像。
        ,updateBackground:function(){
            var _child=this;
            _child.clear_background();
            if(data_bg_trans.angle==0||data_bg_trans.angle==360){
                 //context.drawImage(img,0,0,img.width,img.height,imgX,imgY,img.width*imgScale,img.height*imgScale);
//                context_bg.drawImage(image_background,data_bg.location.x,data_bg.location.y,data_bg.originSize.w,data_bg.originSize.h,0,0,data_bg.size.w,data_bg.size.h);
                context_bg.drawImage(image_background,0,0,data_bg_trans.originSize.w,data_bg_trans.originSize.h,data_bg_trans.location.x,data_bg_trans.location.y,data_bg_trans.size.w,data_bg_trans.size.h);
            }
            else if(data_bg_trans.angle==90){
                context_bg.drawImage(data_bg.img_bg_90,0,0,data_bg_trans.originSize.w,data_bg_trans.originSize.h,data_bg_trans.location.x,data_bg_trans.location.y,data_bg_trans.size.w,data_bg_trans.size.h);
            }
            else if(data_bg_trans.angle==180){
                context_bg.drawImage(data_bg.img_bg_180,0,0,data_bg_trans.originSize.w,data_bg_trans.originSize.h,data_bg_trans.location.x,data_bg_trans.location.y,data_bg_trans.size.w,data_bg_trans.size.h);
            }
            else if(data_bg_trans.angle==270){
                context_bg.drawImage(data_bg.img_bg_270,0,0,data_bg_trans.originSize.w,data_bg_trans.originSize.h,data_bg_trans.location.x,data_bg_trans.location.y,data_bg_trans.size.w,data_bg_trans.size.h);
            }
        }
        //--底层api,根据位置移动图片。
        ,MoveBackground:function(pos_x,pos_y){
            data_bg_trans.location.x=pos_x;
            data_bg_trans.location.y=pos_y;
            var _child=this;

            _child.updateBackground();
        }
        //--通过偏移量来移动图片。
        ,MoveBackgroundByDelta:function(delta_x,delta_y){
            var _child=this;
            var _new_x=data_bg_trans.location.x+delta_x;
            var _new_y=data_bg_trans.location.y+delta_y;
            var _min_x=0;
            var _max_x=0;
            //--宽度边界判断。
            //--假如图片宽度竟然比裁剪框要小,那么宽度最小值是0,
            if(data_bg_trans.size.w<=data_cutter.size.w){
                _min_x=0;
                _max_x=appData.panelSize.w-data_bg_trans.size.w;
            }
            //--假如现在的图片宽度比整个容器宽度要小或者要大,那么就有:
            //else if(data_bg_trans.size.w<=appData.panelSize.w){
                //计算得出这时候的x最大值最小值。
                //var __min_x=



            //}
            //--假如现在的图片宽度比整个容器宽度要小或者要大,那么就有:
            else{
                _min_x=data_cutter.size.w-data_bg_trans.size.w;
                _max_x=appData.panelSize.w-data_cutter.size.w;
            }

            //--高度边界判断。
            var _min_y=0;
            var _max_y=0;
            if(data_bg_trans.size.h<=data_cutter.size.h){
                _min_y=0;
                _max_y=appData.panelSize.h-data_bg_trans.size.h;
            }
            else{
                _min_y=data_cutter.size.h-data_bg_trans.size.h;
                _max_y=appData.panelSize.h-data_cutter.size.h;
            }
            if(_new_x<_min_x){
                _new_x=_min_x;
            }
            else if(_new_x>_max_x){
                _new_x=_max_x;
            }
            if(_new_y<_min_y){
                _new_y=_min_y;
            }
            else if(_new_y>_max_y){
                _new_y=_max_y;
            }

            console.log("新的图片位置是:"+_new_x+" X "+_new_y);
            //_child.MoveCutter(_new_x,_new_y);
            _child.MoveBackground(_new_x,_new_y);
        }
        //--底层api,缩放图片。
        ,ScaleBackground:function(_scale){
            //--在缩放前首先计算得到图片的中心点。
            var _middle_x=0;
            var _middle_y=0;

            //--正常状体下面。
            _middle_x=data_bg_trans.location.x+data_bg_trans.size.w/2;
            _middle_y=data_bg_trans.location.y+data_bg_trans.size.h/2;

            data_bg_trans.zoom=_scale;
            data_bg_trans.size.w=data_bg_trans.originSize.w*_scale;
            data_bg_trans.size.h=data_bg_trans.originSize.h*_scale;

            var __x=_middle_x-(data_bg_trans.size.w)/2;
            var __y=_middle_y-(data_bg_trans.size.h)/2;

            data_bg_trans.location.x=__x;
            data_bg_trans.location.y=__y;


            var _child=this;

            _child.updateBackground();
        }
        //--旋转。
        ,RotateImage:function(angle){
            if(angle!=0&&angle!=90&&angle!=180&&angle!=270&&angle!=360){
                console.log("不支持该角度。");
                return;
            }
            var preAngle=data_bg_trans.angle;
            //--计算原本的中心点。
            var _middle_x=0;
            var _middle_y=0;

            //--正常状体下面。
            _middle_x=data_bg_trans.location.x+data_bg_trans.size.w/2;
            _middle_y=data_bg_trans.location.y+data_bg_trans.size.h/2;

            //--那么一旦改变了角度,需要将各个坐标系都转换过来。
            if(angle==0||angle==360){
                data_bg_trans.angle=0;
                data_bg_trans.originSize.w=data_bg.originSize.w;
                data_bg_trans.originSize.h=data_bg.originSize.h;

            }
            else if(angle==90){
                data_bg_trans.angle=90;
                data_bg_trans.originSize.w=data_bg.originSize.h;
                data_bg_trans.originSize.h=data_bg.originSize.w;

            }
            else if(angle==180){
                data_bg_trans.angle=180;
                data_bg_trans.originSize.w=data_bg.originSize.w;
                data_bg_trans.originSize.h=data_bg.originSize.h;
            }
            else if(angle==270){
                data_bg_trans.angle=270;
                data_bg_trans.originSize.w=data_bg.originSize.h;
                data_bg_trans.originSize.h=data_bg.originSize.w;
            }

                data_bg_trans.size.w=data_bg_trans.originSize.w*data_bg_trans.zoom;
                data_bg_trans.size.h=data_bg_trans.originSize.h*data_bg_trans.zoom;
                data_bg_trans.location.x=_middle_x-(data_bg_trans.size.w)/2;
                data_bg_trans.location.y=_middle_y-(data_bg_trans.size.h)/2;


            data_bg_trans.angle=angle;
            var _child=this;

            _child.updateBackground();
        }
        //--初始化操控层。
        ,initOperationLayout:function(){
            var _child=this;
            data_op.panelInfo=innerTools.GetAbsoluteLocationEx(canvas_operation);
            console.log("当前panel的位置信息。");
            console.log(data_op.panelInfo);
            console.log(innerTools.browser);
            if(innerTools.browser.versions.mobile==true||innerTools.browser.versions.mobile==true||innerTools.browser.versions.android||innerTools.browser.versions.iPhone){
                _child._init_mobile_operations();
            }
            else{
                _child._init_pc_operations();
            }

        }
        //--pc上面的控制事件。
        ,_init_pc_operations:function(){
    //o是移动对象
            var _child=this;
            $(canvas_operation).mousedown(function(event){
                if(appData.hasImage==false){
                    return;
                }
                data_op.isInDrag=true;

                console.log("canvas operation mouse down:");
                data_op.panelInfo=innerTools.GetAbsoluteLocationEx(canvas_operation);
                if(data_op.panelInfo.absoluteLeft==0&&data_op.panelInfo.absoluteTop==0&&data_op.panelInfo.offsetHeight==0&&data_op.panelInfo.offsetWidth==0){

                }
                //params.flag = true;
                if (!event) {
                    event = window.event;
                    //防止IE文字选中
                    canvas_operation.onselectstart = function () {
                        return false;
                    }
                }
                var e = event;
                var params_currentX = e.clientX;
                var params_currentY = e.clientY;
                data_op.beginPoint.x=params_currentX;
                data_op.beginPoint.y=params_currentY;
                var realX=params_currentX-data_op.panelInfo.absoluteLeft;
                var realY=params_currentY-data_op.panelInfo.absoluteTop;
             _clearDebug();
            _debug("未处理前,realX realY是:"+realX+" x "+realY);
                //注意,上面没有考虑到有滚动条的状态,现在添加对滚动条的处理。
                            //--notice,假如页面有滚动条,每次都滚到不同位置的话,那么这个定位可能会有问题,于是我们需要进行处理。
            var _scrollTop=$(document).scrollTop();
            if(_scrollTop>0){
                realY=realY+_scrollTop;
            }
            var _scrollLeft=$(document).scrollLeft();
            if(_scrollLeft>0){
                realX=realX+_scrollLeft;
            }
                data_op.point1.x=params_currentX;
                data_op.point1.y=params_currentY;
                data_op.isInCut=_child.isPointInCutter(realX,realY);
            _debug("scroll top left信息:"+_scrollTop+" x "+_scrollLeft);
            _debug("当前panel位置信息是:"+JSON.stringify(data_op.panelInfo));
            _debug("realX realY是:"+realX+" x "+realY);
            _debug("裁剪框位置是:"+JSON.stringify(data_cutter));

                console.log("当前坐标");
                console.log(params_currentX+"x"+params_currentY);
                if(data_op.isInCut){
                    console.log("%c 在裁剪框里面。","color:green;");
                }

            });
            $(document).mouseup(function(){
                if(appData.hasImage==false){
                    return;
                }
                data_op.isInDrag=false;
                console.log("document 鼠标移动上去了。 mouse up");
            });
            $(document).mousemove(function (event) {
                if(appData.hasImage==false){
                    return;
                }
                if(data_op.isInDrag==false){
                    return;
                }
                var e = event ? event : window.event;
                var nowX = e.clientX, nowY = e.clientY;
                console.log("%c 移动事件 mousemove中,位置:"+nowX+" x "+nowY+" 是否在裁剪框里面:"+data_op.isInCut,"color:red");
                // var disX = nowX - params.currentX, disY = nowY - params.currentY;
                //target.style.left = parseInt(params.left) + disX + "px";
                //target.style.top = parseInt(params.top) + disY + "px";
                //--计算位移量。
                data_op.point2.x=nowX;
                data_op.point2.y=nowY;

                var delta_x=data_op.point2.x-data_op.point1.x;
                var delta_y=data_op.point2.y-data_op.point1.y;
                data_op.point1.x=nowX;
                data_op.point1.y=nowY;
                if(data_op.isInCut==true){
                    //--已经
                    console.log("现在在重新渲染裁剪框了。"+delta_x+" x "+delta_y);

                    _child.MoveCutterByDelta(delta_x,delta_y);
                }
                else{
                    _child.MoveBackgroundByDelta(delta_x,delta_y);
                }

            });
        }
        //--手机端移动端的事件。
        ,_init_mobile_operations:function(){
            var _child=this;
        canvas_operation.addEventListener("touchstart",function(event){
            if(appData.hasImage==false){
                return;
            }

        var beginX=event.changedTouches[0].pageX;
        var beginY=event.changedTouches[0].pageY;

                data_op.isInDrag=true;

                console.log("canvas operation touch start");
                data_op.panelInfo=innerTools.GetAbsoluteLocationEx(canvas_operation);
                if(data_op.panelInfo.absoluteLeft==0&&data_op.panelInfo.absoluteTop==0&&data_op.panelInfo.offsetHeight==0&&data_op.panelInfo.offsetWidth==0){
                   // data_op.panelInfo=innerTools.GetAbsoluteLocationEx(canvas_operation);
                }

                var e = event;
                var params_currentX = beginX;
                var params_currentY = beginY;
                data_op.beginPoint.x=params_currentX;
                data_op.beginPoint.y=params_currentY;
                var realX=params_currentX-data_op.panelInfo.absoluteLeft;
                var realY=params_currentY-data_op.panelInfo.absoluteTop;
             _clearDebug();
            _debug("未处理前,realX realY是:"+realX+" x "+realY);

                //注意,上面没有考虑到有滚动条的状态,现在添加对滚动条的处理。
                            //--notice,假如页面有滚动条,每次都滚到不同位置的话,那么这个定位可能会有问题,于是我们需要进行处理。
            var _scrollTop=$(document).scrollTop();
            if(_scrollTop>0){
                //realY=realY+_scrollTop; 移动端不用管,似乎得到的是正确的。
            }
            var _scrollLeft=$(document).scrollLeft();
            if(_scrollLeft>0){
               // realX=realX+_scrollLeft; 移动端不用管,似乎得到的是正确的。
            }

            _debug("scroll top left信息:"+_scrollTop+" x "+_scrollLeft);
            _debug("当前panel位置信息是:"+JSON.stringify(data_op.panelInfo));
            _debug("realX realY是:"+realX+" x "+realY);
            _debug("裁剪框位置是:"+JSON.stringify(data_cutter));

                data_op.point1.x=params_currentX;
                data_op.point1.y=params_currentY;
                data_op.isInCut=_child.isPointInCutter(realX,realY);


                console.log("当前坐标");
                console.log(params_currentX+"x"+params_currentY);
                if(data_op.isInCut){
                    console.log("%c 在裁剪框里面。","color:green;");
                }
            event.preventDefault();
            event.stopPropagation();
    },false);

        canvas_operation.addEventListener("touchmove",function(event){
                if(appData.hasImage==false){
                    return;
                }
                if(data_op.isInDrag==false){
                    return;
                }
                event.preventDefault();
                event.stopPropagation();
                var e = event ? event : window.event;
                var beginX=event.changedTouches[0].pageX;
                var beginY=event.changedTouches[0].pageY;
                var nowX = beginX, nowY = beginY;


                console.log("%c 移动事件 mousemove中,位置:"+nowX+" x "+nowY+" 是否在裁剪框里面:"+data_op.isInCut,"color:red");
                // var disX = nowX - params.currentX, disY = nowY - params.currentY;
                //target.style.left = parseInt(params.left) + disX + "px";
                //target.style.top = parseInt(params.top) + disY + "px";
                //--计算位移量。
                data_op.point2.x=nowX;
                data_op.point2.y=nowY;

                var delta_x=data_op.point2.x-data_op.point1.x;
                var delta_y=data_op.point2.y-data_op.point1.y;
                data_op.point1.x=nowX;
                data_op.point1.y=nowY;
                if(data_op.isInCut==true){
                    //--已经
                    console.log("现在在重新渲染裁剪框了。"+delta_x+" x "+delta_y);

                    _child.MoveCutterByDelta(delta_x,delta_y);
                }
                else{
                    _child.MoveBackgroundByDelta(delta_x,delta_y);
                }

    },false);


        canvas_operation.addEventListener("touchend",function(event){
        if(appData.hasImage==false){
           return;
        }
         data_op.isInDrag=false;
        event.preventDefault();
        event.stopPropagation();
        },false);
        }
        //--判断某个点是否在裁剪框里面。
        ,isPointInCutter:function(point_x,point_y){
            var min_x=data_cutter.location.x;
            var min_y=data_cutter.location.y;
            var max_x=min_x+data_cutter.size.w;
            var max_y=min_y+data_cutter.size.h;

            if(point_x<min_x||point_x>max_x||point_y<min_y||point_y>max_y){
                return false;
            }
            else{
                return true;
            }
        }
        ,_cut_by_sizing:function(callback){
            var __canvas = document.createElement("canvas");
            var __context = __canvas.getContext("2d");
            __canvas.width = data_cutter.size.w;
            __canvas.height = data_cutter.size.h;
            var _image=new Image();
            _image.onload=function(){
                __context.drawImage(_image,data_cutter.location.x,data_cutter.location.y,data_cutter.size.w,data_cutter.size.h,0,0,data_cutter.size.w,data_cutter.size.h);
                if(callback){
                    var _base64=__canvas.toDataURL("image/jpeg");
                    callback(_base64);
                }
            };
            _image.src=canvas_bg.toDataURL("image/jpeg");
        }
        ,_cut_by_ratio:function(callback){
            //--根据当前操作界面上的数据,换算成为图片上真实尺寸。
            var _real_x_in_img=data_cutter.location.x-data_bg_trans.location.x;
            var _real_y_in_img=data_cutter.location.y-data_bg_trans.location.y;
            _real_x_in_img=_real_x_in_img/data_bg_trans.zoom;
            _real_y_in_img=_real_y_in_img/data_bg_trans.zoom;
            var _real_w=data_cutter.size.w/data_bg_trans.zoom;
            var _real_h=data_cutter.size.h/data_bg_trans.zoom;

            var __canvas = document.createElement("canvas");
            var __context = __canvas.getContext("2d");
            __canvas.width = _real_w;
            __canvas.height = _real_h;
            var _image;
            if(data_bg_trans.angle==0||data_bg_trans.angle==360){
                _image=data_bg.img_bg_0;
            }
            else if(data_bg_trans.angle==90){
                _image=data_bg.img_bg_90;
            }
            else if(data_bg_trans.angle==180){
                _image=data_bg.img_bg_180;
            }
            else if(data_bg_trans.angle==270){
                _image=data_bg.img_bg_270;
            }
            __context.drawImage(_image,_real_x_in_img,_real_y_in_img,_real_w,_real_h,0,0,_real_w,_real_h);

            var _base64=__canvas.toDataURL("image/jpeg");
            callback(_base64);


        }
        //--裁剪图像,
        ,cut:function(callback){
            var _child=this;
            if(settings.saveMode=="ratio"){
                _child._cut_by_ratio(callback);
            }
            else{
                _child._cut_by_sizing(callback);
            }
        }



    };

    app.init();




    //初始化控件。
    this.init=function(base64_image_str){
        console.log("初始化。");
        app.init_background(base64_image_str);
        app.updateCutter();
    };
    //--对外接口,移动裁剪框。
    this.MoveCutter=function(pos_x,pos_y){
        //--这里至少需要进行边界碰撞的判断。
        app.MoveCutter(pos_x,pos_y);
    };
    //--对外接口,移动图片。
    this.MoveImage=function(pos_x,pos_y){
        //--这里至少需要进行边界碰撞的判断。
        app.MoveBackground(pos_x,pos_y);
    };
    //--对外接口,缩放图片、
    this.ScaleImage=function(_scale){
        app.ScaleBackground(_scale);
    };
    this.RotateImage=function(angle){
        app.RotateImage(angle);
    };
    //--是否已经有图片了。
    this.hasImage=function(){
        return appData.hasImage;
    };
    this.show_data_cutter=function(){
      console.log(data_cutter);
    };
    //--获取目前的角度。
    this.getImageAngle=function(){
        return data_bg_trans.angle;
    };
    //--获取图片的缩放倍率。
    this.getImageRotation=function(){
        return data_bg_trans.angle;
    };
    //--获取缩放倍率。
    this.getImageZoom=function(){
        return data_bg_trans.zoom;
    };
    this.cutImg=function(callback){
      app.cut(callback);
    };


}

demo脚本

var _e_width=$(window).width()-200;
var _e_height=$(window).height()-200;
if(_e_width>800){
    _e_width=800;
}
else if(_e_width<450){
    _e_width=450;
}
/*
if(_e_height>600){
    _e_height=600;
}
else if(_e_height<400){
    _e_height=400;
}*/
var _image_editor={};
try{
   _image_editor =new ImageEditor({
        cutWidth:150 //裁剪框的宽度。
        ,cutHeight:200 //裁剪框的高度
        ,containerWidth:_e_width //容器宽度
        ,containerHeight:_e_height //容器高度
        ,imageShowWidth:400 //图片默认显示的宽度【会按照要求的宽度及高度等比缩放】
        ,imageShowHeight:500 //图片默认显示的高度【会按照要求的宽度及高度等比缩放】
        ,containerElement:$("#div_cropper") //容器的默认元素,jquery元素或者原生dom元素。
});
}
catch (ed){
    alert(ed);
}
    //--逻辑方法定义
$("#uploadImage").change(function(){

    if (document.getElementById("uploadImage").files.length === 0) {
        layer.alert("请选择图片!",3);
        return; }
    var oFile = document.getElementById("uploadImage").files[0];
    //if (!rFilter.test(oFile.type)) { alert("You must select a valid image file!"); return; }

    /*  if(oFile.size>5*1024*1024){
     message(myCache.par.lang,{cn:"照片上传:文件不能超过5MB!请使用容量更小的照片。",en:"证书上传:文件不能超过100K!"})
     changePanel("result");
     return;
     }*/
    if(!new RegExp("(jpg|jpeg|gif|png)+","gi").test(oFile.type)){
        layer.alert("照片上传:文件类型必须是JPG、JPEG、PNG或GIF!",3);
        return;
    }

       var reader = new FileReader();

        reader.onload =function(e){
                           var _img_str=e.target.result;
                // img 元素
                _image_editor.init(_img_str);
        };
    reader.readAsDataURL(oFile);
    return;
});

$("#btn_zoom_out").click(function(){
    if(_image_editor.hasImage()==false){
        alert("请先选择图片!");
        return;

    }
    var _zoom=_image_editor.getImageZoom();
    if(_zoom<0.5){
        alert("最小倍率是0.5,无法继续缩小");
        return;
    }
    _image_editor.ScaleImage(_zoom-0.2);
});

$("#btn_zoom_in").click(function(){
    if(_image_editor.hasImage()==false){
        alert("请先选择图片!");
        return;

    }
    var _zoom=_image_editor.getImageZoom();
    if(_zoom>4){
        alert("最大倍率是4,无法继续放大");
        return;
    }
    _image_editor.ScaleImage(_zoom+0.2);
});

$("#btn_turn_left").click(function(){
    if(_image_editor.hasImage()==false){
        alert("请先选择图片!");
        return;

    }
    var _angle=_image_editor.getImageAngle();
    _angle=(_angle+360)-90;
    _angle=(_angle)%360;
    _image_editor.RotateImage(_angle);
});


$("#btn_turn_right").click(function(){
    if(_image_editor.hasImage()==false){
        alert("请先选择图片!");
        return;

    }
    var _angle=_image_editor.getImageAngle();
    _angle=(_angle+0)+90;
    _angle=(_angle)%360;
    _image_editor.RotateImage(_angle);
});

$("#btn_save").click(function(){
    if(_image_editor.hasImage()==false){
        alert("请先选择图片!");
        return;

    }
    _image_editor.cutImg(function(imgStr){
        $("#preview_img").attr("src",imgStr);
    });
});

未完待续,下一篇是实际应用效果

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
当然,我将为您提供一个更详细的代码示例来实现加载图片、拖拽、缩放和涂鸦功能的自定义控件。以下是一个完整的示例: ```java import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.View; public class CustomImageView extends View { private Bitmap imageBitmap; private float imageX, imageY; private float scaleFactor = 1.0f; private Path drawingPath; private Paint drawingPaint; private float lastTouchX, lastTouchY; private GestureDetector gestureDetector; private ScaleGestureDetector scaleGestureDetector; public CustomImageView(Context context) { super(context); init(); } public CustomImageView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public CustomImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { // 初始化画笔和绘制路径 drawingPaint = new Paint(); drawingPaint.setAntiAlias(true); drawingPaint.setStyle(Paint.Style.STROKE); drawingPaint.setStrokeWidth(5); drawingPath = new Path(); // 初始化手势检测器 gestureDetector = new GestureDetector(getContext(), new MyGestureListener()); scaleGestureDetector = new ScaleGestureDetector(getContext(), new MyScaleGestureListener()); } public void loadImage(int resId) { // 加载图片 imageBitmap = BitmapFactory.decodeResource(getResources(), resId); invalidate(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 绘制图片 if (imageBitmap != null) { canvas.save(); canvas.scale(scaleFactor, scaleFactor); canvas.drawBitmap(imageBitmap, imageX, imageY, null); canvas.restore(); } // 绘制涂鸦 canvas.drawPath(drawingPath, drawingPaint); } @Override public boolean onTouchEvent(MotionEvent event) { // 处理触摸事件 gestureDetector.onTouchEvent(event); scaleGestureDetector.onTouchEvent(event); float touchX = event.getX() / scaleFactor; float touchY = event.getY() / scaleFactor; switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // 记录上一次的触摸位置 lastTouchX = touchX; lastTouchY = touchY; // 开始涂鸦 drawingPath.moveTo(touchX, touchY); break; case MotionEvent.ACTION_MOVE: // 计算涂鸦路径 float dx = Math.abs(touchX - lastTouchX); float dy = Math.abs(touchY - lastTouchY); if (dx >= 4 || dy >= 4) { drawingPath.quadTo(lastTouchX, lastTouchY, (touchX + lastTouchX) / 2, (touchY + lastTouchY) / 2); lastTouchX = touchX; lastTouchY = touchY; } break; case MotionEvent.ACTION_UP: // 结束涂鸦 drawingPath.lineTo(touchX, touchY); break; } invalidate(); return true; } private class MyGestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { // 实现拖拽功能的逻辑 imageX -= distanceX; imageY -= distanceY; invalidate(); return true; } } private class MyScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { @Override public boolean onScale(ScaleGestureDetector detector) { // 实现缩放功能的逻辑 scaleFactor *= detector.getScaleFactor(); scaleFactor = Math.max(0.1f, Math.min(scaleFactor, 5.0f)); invalidate(); return true; } } } ``` 在这个示例中,我们创建了一个名为 `CustomImageView` 的自定义控件。我们使用 `Bitmap` 类来存储加载的图片,并使用 `Path` 和 `Paint` 类来实现涂鸦功能。在触摸事件处理中,我们通过手势检测器来实现拖拽和缩放功能。 您可以在您的项目中使用此代码作为起点,并根据您的需求进行修改和扩展。希望对您有所帮助!如果您有任何其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值