本文将介绍基于Angular的图片裁剪上传插件。
github: https://github.com/licaomeng/angular-image-upload
希望大家帮忙Star一下我的项目,我会持续更新~O(∩_∩)O

插件效果如下:

该插件的图片裁剪是通过图片的放大、缩小、拖动完成的。而不同于我们通常所见到的拖动剪裁范围,进行的图片剪裁。这是一种反向思维。

imgZoomCanvas.js

图片的放大、缩小、拖动,全部是在html5的Canvas上面完成的。实现该算法的核心代码封装在 imgZoomCanvas.js 里面。

/** * Created by Caomeng Li on 8/23/2016. */
angular.module('appModule')
    .factory('imgZoomCanvas',[function () {

        //singleton
        var INSTANCE = null;

        var getInstance = function (options) {
            return INSTANCE || ( INSTANCE = new CanvasZoom(options) );
        }

        var destroyInstance = function () {
            if (INSTANCE) {
                INSTANCE = null;
            }
        }

        var stopAnimate = function () {
            return INSTANCE ? INSTANCE.stopAnimate() : null;
        }

        var onZoom = function (zoom) {
            return INSTANCE ? INSTANCE.doZoom(zoom) : null;
        }

        var CanvasZoom = function (options) {
            if (!options || !options.canvas) {
                throw 'CanvasZoom constructor: missing arguments canvas';
            }
            if (!options.image) {
                throw 'CanvasZoom constructor: missing arguments image';
            }

            this.canvas = options.canvas;
            this.image = options.image;
            this.currentAnimationId = 0;
            this.canvas.width = this.canvas.clientWidth;
            this.canvas.height = this.canvas.clientHeight;
            this.context = this.canvas.getContext('2d');

            this.lastX = 0;
            this.lastY = 0;

            this.position = {
                x: 0,y: 0
            };

            this.initPosition = {
                x: 0,y: 0
            }

            this.scale = {
                x: 1,y: 1
            };

            this.initScale = {
                x: 1,y: 1
            };

            this.init = false;

            this.checkRequestAnimationFrame();
            this.currentAnimationId = requestAnimationFrame(this.animate.bind(this));

            this.setEventListeners();
        }

        CanvasZoom.prototype = {
            stopAnimate: function () {
                cancelAnimationFrame(this.currentAnimationId);
            },animate: function () {
                this.context.clearRect(0,0,this.canvas.width,this.canvas.height);
                var imgWidth = this.image.width,imgHeight = this.image.height;
                if (!this.init) {
                    if (imgWidth > imgHeight) {
                        this.scale.x = this.scale.y = this.canvas.height / imgHeight;
                    } else {
                        this.scale.x = this.scale.y = this.canvas.width / imgWidth;
                    }
                    this.initScale.x = this.scale.x;
                    this.initScale.y = this.scale.y;
                }
                var currentWidth = (this.image.width * this.scale.x);
                var currentHeight = (this.image.height * this.scale.y);

                if (!this.init) {
                    if (imgWidth > imgHeight) {
                        this.position.x = (currentWidth - this.canvas.width) / 2;
                        this.position.x = this.position.x > 0 ? -this.position.x : this.position.x;
                    } else {
                        this.position.y = (currentHeight - this.canvas.height) / 2;
                        this.position.y = this.position.y > 0 ? -this.position.y : this.position.y;
                    }
                    this.initPosition.x = this.position.x;
                    this.initPosition.y = this.position.y
                    this.init = true;
                }

                this.context.drawImage(this.image,this.position.x,this.position.y,currentWidth,currentHeight);
                this.currentAnimationId = requestAnimationFrame(this.animate.bind(this));
            },doZoom: function (zoom) {
                if (!zoom) return;

                //new scale
                var currentScale = this.scale.x;
                var newScale = this.scale.x + zoom * this.scale.x / 100;

                //some helpers
                var deltaScale = newScale - currentScale;
                var currentWidth = (this.image.width * this.scale.x);
                var currentHeight = (this.image.height * this.scale.y);
                var deltaWidth = this.image.width * deltaScale;
                var deltaHeight = this.image.height * deltaScale;

                //by default scale doesnt change position and only add/remove pixel to right and bottom
                //so we must move the image to the left to keep the image centered
                //ex: coefX and coefY = 0.5 when image is centered <=> move image to the left 0.5x pixels added to the right
                var canvasmiddleX = this.canvas.clientWidth / 2;
                var canvasmiddleY = this.canvas.clientHeight / 2;
                var xonmap = (-this.position.x) + canvasmiddleX;
                var yonmap = (-this.position.y) + canvasmiddleY;
                var coefX = -xonmap / (currentWidth);
                var coefY = -yonmap / (currentHeight);
                var newPosX = this.position.x + deltaWidth * coefX;
                var newPosY = this.position.y + deltaHeight * coefY;

                //edges cases
                var newWidth = currentWidth + deltaWidth;
                var newHeight = currentHeight + deltaHeight;

                if (newPosX > 0) {
                    newPosX = 0;
                }
                if (newPosX + newWidth < this.canvas.clientWidth) {
                    newPosX = this.canvas.clientWidth - newWidth;
                }

                if (newHeight < this.canvas.clientHeight) return;
                if (newPosY > 0) {
                    newPosY = 0;
                }
                if (newPosY + newHeight < this.canvas.clientHeight) {
                    newPosY = this.canvas.clientHeight - newHeight;
                }

                //finally affectations
                this.scale.x = newScale;
                this.scale.y = newScale;
                this.position.x = newPosX;
                this.position.y = newPosY;

                //edge cases
                if (this.scale.x < this.initScale.x) {
                    this.scale.x = this.initScale.x;
                    this.scale.y = this.initScale.x;
                    this.position.x = this.initPosition.x;
                    this.position.y = this.initPosition.y;
                }
            },doMove: function (relativeX,relativeY) {
                if (this.lastX && this.lastY) {

                    console.log('relativeX',relativeX);
                    console.log('relativeY',relativeY);

                    console.log('this.lastX',this.lastX);
                    console.log('this.lastY',this.lastY);

                    var deltaX = relativeX - this.lastX;
                    var deltaY = relativeY - this.lastY;
                    console.log('deltaX',deltaX);
                    console.log('deltaY',deltaY);

                    var currentWidth = (this.image.width * this.scale.x);
                    var currentHeight = (this.image.height * this.scale.y);

                    this.position.x += deltaX;
                    this.position.y += deltaY;
                    console.log('this.position.x',this.position.x);
                    console.log('this.position.y',this.position.y);

                    // edge cases
                    if (this.position.x >= 0) {
                        this.position.x = 0;
                    } else if (this.position.x < 0 && this.position.x + currentWidth < this.canvas.width) {
                        this.position.x = this.canvas.width - Math.round(currentWidth);
                    }

                    if (this.position.y >= 0) {
                        this.position.y = 0;
                    } else if (this.position.y < 0 && this.position.y + currentHeight < this.canvas.height) {
                        this.position.y = this.canvas.height - Math.round(currentHeight);
                    }
                }
                this.lastX = relativeX;
                this.lastY = relativeY;
            },setEventListeners: function () {
                this.canvas.addEventListener('mousedown',function (e) {
                    this.mdown = true;
                    this.lastX = 0;
                    this.lastY = 0;
                }.bind(this));

                this.canvas.addEventListener('mouseup',function (e) {
                    this.mdown = false;
                }.bind(this));

                this.canvas.addEventListener('mousemove',function (e) {
                    var relativeX = e.pageX - this.canvas.getBoundingClientRect().left;
                    var relativeY = e.pageY - this.canvas.getBoundingClientRect().top;

                    if (e.target == this.canvas && this.mdown) {
                        this.doMove(relativeX,relativeY);
                    }

                    if (relativeX <= 0 || relativeX >= this.canvas.clientWidth || relativeY <= 0 || relativeY >= this.canvas.clientHeight) {
                        this.mdown = false;
                    }
                }.bind(this));
            },checkRequestAnimationFrame: function () {
                var lastTime = 0;
                var vendors = ['ms','moz','webkit','o'];
                for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
                    window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
                    window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame']
                        || window[vendors[x] + 'CancelRequestAnimationFrame'];
                }

                if (!window.requestAnimationFrame) {
                    window.requestAnimationFrame = function (callback,element) {
                        var currTime = new Date().getTime();
                        var timetoCall = Math.max(0,16 - (currTime - lastTime));
                        var id = window.setTimeout(function () {
                            callback(currTime + timetoCall);
                        },timetoCall);
                        lastTime = currTime + timetoCall;
                        return id;
                    };
                }

                if (!window.cancelAnimationFrame) {
                    window.cancelAnimationFrame = function (id) {
                        clearTimeout(id);
                    };
                }
            }
        }
        return {
            getInstance: getInstance,destroyInstance: destroyInstance,stopAnimate: stopAnimate,onZoom: onZoom
        };
    }]);

imgZoomCanvas开放出了四个方法:

getInstance (生成单体实例) destroyInstance (销毁单体实例) stopAnimate (停止requestAnimationFrame产生的动画) onZoom (图片缩放。传入缩放的参数,放大为正,缩小为负)

imgUploader.js

接下来介绍imgUpload这个directive,就是上面那个GIF看到的图片上传组件。逻辑代码封装在imgUploader.js里面你可以自己定制这四个方法:

deleteAvatar(移除当前图片,并且上传到服务器) uploadAvatar(上传当前图片) zoomOut zoomIn (它们负责图片缩放的步长,为一正一负,参数可以自己慢慢调整。)

另外该directive开放出一个回调方法onUpload,可以在你的业务controller里面实现相关图片上传logic。onUpload有三个参数:

(image,isDelete,isHasAvatar)

第一个image是从Canvas上面导出的base64具体的实现在刚才介绍的deleteAvatar中:
canvas.toDataURL('png')

imgUploader对应的html:

<div id="img-uploader">
    <canvas class="avatar" id="myCanvas" width="150" height="150"></canvas>
    <div style="position: relative;left:156px;top:-1px">
        <div id="delete-avatar" ng-click="deleteAvatar()" class="delete-avatar">
            <img src="./image/delete.png">
        </div>
        <div class="edit-avatar">
            <img src="./image/edit.png">

            <div id="container" class="container">
                <input class="file-picker" id="file" type="file"/>
            </div>
        </div>
        <div id="upload-avatar" ng-show="fileSelected" ng-click="uploadAvatar()" class="upload-avatar">
            <img src="./image/upload.png">
        </div>
        <div id="zoom-out" ng-show="fileSelected" ng-click="zoomOut()" class="zoom-out">
            <img src="./image/zoom_out.png">
        </div>
        <div id="zoom-in" ng-show="fileSelected" ng-click="zoomIn()" class="zoom-in">
            <img src="./image/zoom_in.png">
        </div>
    </div>
</div>

最后就是我们的页面逻辑代码了,页面controller中只需要实现上面提到的回调方法onUpload即可:

$scope.upload = function (image,isHasAvatar) {
      // Write your image upload logic here
 }

页面html只需要加入我们刚才的directive imageUploader:

<img-uploader on-upload="upload(image,isHasAvatar)" image="image" is-editable="isEditable"></img-uploader>

Angular 图片裁剪上传插件的更多相关文章

  1. HTML实现代码雨源码及效果示例

    这篇文章主要介绍了HTML实现代码雨源码及效果示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  2. 用canvas做一个DVD待机动画的实现代码

    这篇文章主要介绍了用canvas做一个DVD待机动画的实现代码的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  3. HTML5自定义视频播放器源码

    这篇文章主要介绍了HTML5自定义视频播放器源码,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下

  4. Html5 滚动穿透的方法

    这篇文章主要介绍了Html5 滚动穿透的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  5. HTML5自定义mp3播放器源码

    这篇文章主要介绍了HTML5自定义mp3播放器源码,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下

  6. css绝对定位如何在不同分辨率下的电脑正常显示定位位置?(一定要看!)

    这篇文章主要介绍了css绝对定位如何在不同分辨率下的电脑正常显示定位位置,本文首先解释了常见的电脑分辨率,为了页面在不同的分辨率下正常显示,要给页面一个安全宽度,再去使用绝对定位,具体操作步骤大家可查看下文的详细讲解,感兴趣的小伙伴们可以参考一下。

  7. CSS中实现动画效果-附案例

    这篇文章主要介绍了 CSS中实现动画效果并附上案例代码及实现效果,就是CSS动画样式处理,动画声明需要使用@keyframes name,后面的name是人为定义的动画名称,下面我们来看看文章的具体实现内容吧,需要的小伙伴可以参考一下

  8. html5默认气泡修改的代码详解

    这篇文章主要介绍了html5默认气泡修改的代码详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  9. 解析html5 canvas实现背景鼠标连线动态效果代码

    流行的动态背景连线特效。今天小编通过实例代码给大家解析html5 canvas实现背景鼠标连线动态效果,感兴趣的朋友一起看看吧

  10. Html5移动端适配IphoneX等机型的方法

    这篇文章主要介绍了Html5移动端适配IphoneX等机型的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

随机推荐

  1. Angular2 innerHtml删除样式

    我正在使用innerHtml并在我的cms中设置html,响应似乎没问题,如果我这样打印:{{poi.content}}它给了我正确的内容:``但是当我使用[innerHtml]=“poi.content”时,它会给我这个html:当我使用[innerHtml]时,有谁知道为什么它会剥离我的样式Angular2清理动态添加的HTML,样式,……

  2. 为Angular根组件/模块指定@Input()参数

    我有3个根组件,由根AppModule引导.你如何为其中一个组件指定@input()参数?也不由AppModalComponent获取:它是未定义的.据我所知,你不能将@input()传递给bootstraped组件.但您可以使用其他方法来做到这一点–将值作为属性传递.index.html:app.component.ts:

  3. angular-ui-bootstrap – 如何为angular ui-bootstrap tabs指令指定href参数

    我正在使用角度ui-bootstrap库,但我不知道如何为每个选项卡指定自定义href.在角度ui-bootstrap文档中,指定了一个可选参数select(),但我不知道如何使用它来自定义每个选项卡的链接另一种重新定义问题的方法是如何使用带有角度ui-bootstrap选项卡的路由我希望现在还不算太晚,但我今天遇到了同样的问题.你可以通过以下方式实现:1)在控制器中定义选项卡href:2)声明一个函数来改变控制器中的散列:3)使用以下标记:我不确定这是否是最好的方法,我很乐意听取别人的意见.

  4. 离子框架 – 标签内部的ng-click不起作用

    >为什么标签标签内的按钮不起作用?>但是标签外的按钮(登陆)工作正常,为什么?>请帮我解决这个问题.我需要在点击时做出回复按钮workingdemo解决方案就是不要为物品使用标签.而只是使用divHTML

  5. Angular 2:将值传递给路由数据解析

    我正在尝试编写一个DataResolver服务,允许Angular2路由器在初始化组件之前预加载数据.解析器需要调用不同的API端点来获取适合于正在加载的路由的数据.我正在构建一个通用解析器,而不是为我的许多组件中的每个组件设置一个解析器.因此,我想在路由定义中传递指向正确端点的自定义输入.例如,考虑以下路线:app.routes.ts在第一个实例中,解析器需要调用/path/to/resourc

  6. angularjs – 解释ngModel管道,解析器,格式化程序,viewChangeListeners和$watchers的顺序

    换句话说:如果在模型更新之前触发了“ng-change”,我可以理解,但是我很难理解在更新模型之后以及在完成填充更改之前触发函数绑定属性.如果您读到这里:祝贺并感谢您的耐心等待!

  7. 角度5模板形式检测形式有效性状态的变化

    为了拥有一个可以监听其包含的表单的有效性状态的变化的组件并执行某些组件的方法,是reactiveforms的方法吗?

  8. Angular 2 CSV文件下载

    我在springboot应用程序中有我的后端,从那里我返回一个.csv文件WheniamhittingtheURLinbrowsercsvfileisgettingdownloaded.现在我试图从我的角度2应用程序中点击此URL,代码是这样的:零件:服务:我正在下载文件,但它像ActuallyitshouldbeBook.csv请指导我缺少的东西.有一种解决方法,但您需要创建一个页面上的元

  9. angularjs – Angular UI-Grid:过滤后如何获取总项数

    提前致谢:)你应该避免使用jQuery并与API进行交互.首先需要在网格创建事件中保存对API的引用.您应该已经知道总行数.您可以使用以下命令获取可见/已过滤行数:要么您可以使用以下命令获取所选行的数量:

  10. angularjs – 迁移gulp进程以包含typescript

    或者我应该使用tsc作为我的主要构建工具,让它解决依赖关系,创建映射文件并制作捆绑包?

返回
顶部