首页 web前端 js教程 JavaScript图片裁剪的无变形的示例代码分享(图)

JavaScript图片裁剪的无变形的示例代码分享(图)

Mar 13, 2017 pm 04:51 PM

最近浏览了不少网站的图片裁切效果,大部分的做法如下图所示(借用一张脚本之家的图片),通过改变裁切框的大小来选取合适的位置。

但本文介绍的是另外一种裁切方式,裁切框由开发者决定,图片大小由用户决定,通过缩放、拖动图片来选取合适位置,并且在这一过程中始终保持图片宽高比,如右上图。

这样做法主要有以下优点:

  1. 裁切框的宽高与跟实际使用的处宽高比一致,防止出现图片变形问题

  2. 不限制图片的显示大小,保证图片原始比例,通过缩放可得到原始尺寸

  3. 对于局部的裁切更加友好,比如截取一张高清图片中很小的一个部位,我们只需将图片放大并拖动到裁切框内即可,而其他方式需要将裁切框调整的非常小,不利于用户操作

说完了有点也该说说缺点,缺点就是难度增大了一个数量级。。。。

主体思路是利用两张图片,将他们绝对定位,一张放在裁切框内一张放在裁切框外并设置透明效果,裁切框overflow为hidden,时刻保持两张图片的绝对同步。

<p class="jimu-crop-image" data-dojo-attach-point="cropSection">
    <p class="viewer-box" data-dojo-attach-point="viewerBox">
        <p class="viewer-content" data-dojo-attach-point="viewerContent">
            <img class="viewer-image hide-image" data-dojo-attach-point="viewerImage" src="">
        </p>
        <img class="base-image hide-image" data-dojo-attach-point="baseImage" data-dojo-attach-event="mousedown:_onViewerMouseDown,mouseup:_onViewerMouseUp">

        <p class="controller">
            <p class="zoom-out" data-dojo-attach-event="click:_onZoomOutClick">-</p>
            <p class="slider" data-dojo-attach-point="sliderNode">
                <p class="button" data-dojo-attach-point="sliderButton" data-dojo-attach-event="mousedown:_onSliderMouseDown,mouseup:_onSliderMouseUp"></p>
                <p class="horizontal"></p>
            </p>
            <p class="zoom-in" data-dojo-attach-event="click:_onZoomInClick">+</p>
        </p>
    </p>
</p>
登录后复制

首先在postCreate中绑定document的mousemove跟mousedown事件,在鼠标离开工作区后仍可以继续拖动或缩放。接下来的主要工作在startup跟_init函数中。不熟悉dojo的道友只要知道postCreate会在startup之前执行即可。

startup: function() {
                var timeOut = /data:image\/(.*);base64/.test(this.imageSrc) ? 50 : 500;
                var tic = lang.hitch(this, function() {
                    var imageStyle = html.getComputedStyle(this.baseImage);
                    var imageWidth = parseFloat(imageStyle.width);
                    console.log(&#39;image width&#39;, imageWidth);
                    if (isFinite(imageWidth) && imageWidth > 0) {
                        this._init();
                    } else {
                        setTimeout(tic, timeOut);
                    }
                });

                setTimeout(tic, timeOut);
            },

_init: function() {
                debugger;
                var cropSectionStyle = html.getComputedStyle(this.cropSection);
                var cropSectionContentBox = html.getContentBox(this.cropSection);
                var imageStyle = html.getComputedStyle(this.baseImage);
                var imageWidth = parseFloat(imageStyle.width);
                var imageHeight = parseFloat(imageStyle.height);
                var imageRadio = imageWidth / imageHeight;

                this._maxImageWidth = imageWidth;
                this._maxImageHeight = imageHeight;

                if (imageHeight < this.realHeight && imageWidth < this.realWidth) {
                    alert(&#39;image is too smaller to display&#39;);
                    return;
                }

                //create a box which keep the ratio of width and height to full fill the content of popup
                this.idealWidth = this.realWidth;
                this.idealHeight = this.realHeight;

                this.ratio = this.ratio ? this.ratio : this.realWidth / this.realHeight;
                if (this.ratio >= 1) {
                    if (this.realWidth <= cropSectionContentBox.w) {
                        this.idealWidth += (cropSectionContentBox.w - this.realWidth) / 2;
                    } else {
                        this.idealWidth = cropSectionContentBox.w;
                    }
                    this.idealHeight = this.idealWidth / this.ratio;
                } else {
                    if (this.realHeight <= cropSectionContentBox.h) {
                        this.idealHeight += (cropSectionContentBox.h - this.idealHeight) / 2;
                    } else {
                        this.idealHeight = cropSectionContentBox.h;
                    }
                    this.idealWidth = this.idealHeight * this.ratio;
                }

                html.setStyle(this.viewerBox, {
                    width: this.idealWidth + &#39;px&#39;,
                    height: this.idealHeight + &#39;px&#39;
                });

                var paddingTop = Math.abs((parseFloat(cropSectionStyle.height) - this.idealHeight) / 2);
                html.setStyle(this.cropSection, {
                    &#39;paddingTop&#39;: paddingTop + &#39;px&#39;,
                    &#39;paddingBottom&#39;: paddingTop + &#39;px&#39;
                });

                // keep original ratio of image
                if (imageRadio >= 1) {
                    if (this.idealHeight * imageRadio >= this.idealWidth) {
                        html.setStyle(this.viewerImage, &#39;height&#39;, this.idealHeight + &#39;px&#39;);
                        html.setStyle(this.baseImage, &#39;height&#39;, this.idealHeight + &#39;px&#39;);
                    } else {
                        var properlyHeight = this._findProperlyValue(0, this.idealWidth, this.idealWidth, function(p) {
                            return p * imageRadio;
                        });
                        html.setStyle(this.viewerImage, &#39;height&#39;, properlyHeight + &#39;px&#39;);
                        html.setStyle(this.baseImage, &#39;height&#39;, properlyHeight + &#39;px&#39;);
                    }
                } else {
                    if (this.idealWidth / imageRadio >= this.idealHeight) {
                        html.setStyle(this.viewerImage, &#39;width&#39;, this.idealWidth + &#39;px&#39;);
                        html.setStyle(this.baseImage, &#39;width&#39;, this.idealWidth + &#39;px&#39;);
                    } else {
                        var properlyWidth = this._findProperlyValue(0, this.idealHeight, this.idealHeight, function(p) {
                            return p / imageRadio;
                        });
                        html.setStyle(this.viewerImage, &#39;width&#39;, properlyWidth + &#39;px&#39;);
                        html.setStyle(this.baseImage, &#39;width&#39;, properlyWidth + &#39;px&#39;);
                    }
                }

                query(&#39;.hide-image&#39;, this.domNode).removeClass(&#39;hide-image&#39;);

                imageStyle = html.getComputedStyle(this.baseImage);
                imageWidth = parseFloat(imageStyle.width);
                imageHeight = parseFloat(imageStyle.height);
                this._minImageWidth = imageWidth;
                this._minImageHeight = imageHeight;

                this._currentImageWidth = imageWidth;
                this._currentImageHeight = imageHeight;

                this._currentTop = -(imageHeight - this.idealHeight) / 2;
                this._currentLeft = -(imageWidth - this.idealWidth) / 2;
                html.setStyle(this.baseImage, {
                    top: this._currentTop + &#39;px&#39;,
                    left: this._currentLeft + &#39;px&#39;
                });
                html.setStyle(this.viewerImage, {
                    top: this._currentTop + &#39;px&#39;,
                    left: this._currentLeft + &#39;px&#39;
                });
                //sometimes zoomratio < 1; it&#39;s should be not allowed to zoom
                this._zoomRatio = this._maxImageWidth / this._minImageWidth;

                if (!this._latestPercentage) {
                    this._latestPercentage = 0;
                }
            },
登录后复制

这里面做了以下几件事:

  1. 等待图片加载完毕,获取图片的原始尺寸,后续计算缩放因子时会用到

  2. 在保证裁切区域宽高比的情况下,让裁切区域尽量的填满工作区。这里裁切工作最重要的就是防止图片变形,所以只要保证宽高比一致可以将裁切区域适当放大。

  3. 保持图片原始宽高比的前提下,让图片尽量接近裁切框

  4. 机上计算完成后设置图片初始位置,让裁切框相对图片居中

平移的过程比较简单,只需要记录移动过程中鼠标的相对位置变化,不断改变图片左上角的left跟top即可,在dragstart跟selectstart事件中preventDefault防止出现元素被选中变蓝。

_resetImagePosition: function(clientX, clientY) {
                var delX = clientX - this._currentX;
                var delY = clientY - this._currentY;

                if (this._currentTop + delY >= 0) {
                    html.setStyle(this.baseImage, &#39;top&#39;, 0);
                    html.setStyle(this.viewerImage, &#39;top&#39;, 0);
                    this._currentY = clientY;
                    this._currentTop = 0;
                } else if (this._currentTop + delY <= this._maxOffsetTop) {
                    html.setStyle(this.baseImage, &#39;top&#39;, this._maxOffsetTop + &#39;px&#39;);
                    html.setStyle(this.viewerImage, &#39;top&#39;, this._maxOffsetTop + &#39;px&#39;);
                    this._currentY = clientY;
                    this._currentTop = this._maxOffsetTop;
                } else {
                    html.setStyle(this.baseImage, &#39;top&#39;, this._currentTop + delY + &#39;px&#39;);
                    html.setStyle(this.viewerImage, &#39;top&#39;, this._currentTop + delY + &#39;px&#39;);
                    this._currentY = clientY;
                    this._currentTop += delY;
                }

                if (this._currentLeft + delX >= 0) {
                    html.setStyle(this.baseImage, &#39;left&#39;, 0);
                    html.setStyle(this.viewerImage, &#39;left&#39;, 0);
                    this._currentX = clientX;
                    this._currentLeft = 0;
                } else if (this._currentLeft + delX <= this._maxOffsetLeft) {
                    html.setStyle(this.baseImage, &#39;left&#39;, this._maxOffsetLeft + &#39;px&#39;);
                    html.setStyle(this.viewerImage, &#39;left&#39;, this._maxOffsetLeft + &#39;px&#39;);
                    this._currentX = clientX;
                    this._currentLeft = this._maxOffsetLeft;
                } else {
                    html.setStyle(this.baseImage, &#39;left&#39;, this._currentLeft + delX + &#39;px&#39;);
                    html.setStyle(this.viewerImage, &#39;left&#39;, this._currentLeft + delX + &#39;px&#39;);
                    this._currentX = clientX;
                    this._currentLeft += delX;
                }
            },
登录后复制

缩放的主要原则就是保持裁剪框的中心点在缩放前后的相对位置不变

为了将缩放后的原裁切框的中心点移回原位,我们需要计算两中值:图片大小变化量,图片左上角移动量。

var delImageWidth = this._minImageWidth * (this._zoomRatio - 1) * leftPercentage / 100;
var delImageHeight = this._minImageHeight * (this._zoomRatio - 1) * leftPercentage / 100;

var imageStyle = html.getComputedStyle(this.baseImage);
                this._currentLeft = parseFloat(imageStyle.left);
                this._currentTop = parseFloat(imageStyle.top);
var delImageLeft = (Math.abs(this._currentLeft) + this.idealWidth / 2) *
                    ((this._minImageWidth + delImageWidth) / this._currentImageWidth - 1);
var delImageTop = (Math.abs(this._currentTop) + this.idealHeight / 2) *
                    ((this._minImageWidth + delImageWidth) / this._currentImageWidth - 1);
登录后复制

其中_zoomRatio = _maxImageWidth / _minImageWidth; _maxImageWidth为图片原始大小,_minImageWidth是让图片接近裁切框的最小宽度。

leftPercentage为滑动按钮相对滑动条的位移百分比。

_currentLeft、_currentTop是本次缩放前图片相对裁切框的绝对位置(position:absolute)。

_currentImageWidth、_currentImageHeight是本次缩放前图片的大小。

剩下要做的是防止裁切框内出现空白现象,假设用户放大图片,将图片拖放到边界与裁切框边界重合,这时缩小图片的话裁切框内便会出现空白。为了防止这种情况我们也需要做相应处理。

当图片左上边界与裁切框左上边界重合时,无论如何缩小,image的left、top始终为零,只改变图片大小。

当图片右下边界与裁切框右下边界重合时,根据图片大小与裁切框大小可以计算出合适的left跟top

//prevent image out the crop box
                if (leftPercentage - _latestPercentage >= 0) {
                    console.log(&#39;zoomin&#39;);
                    html.setStyle(this.baseImage, {
                        top: this._currentTop -delImageTop + &#39;px&#39;,
                        left: this._currentLeft -delImageLeft + &#39;px&#39;
                    });
                    html.setStyle(this.viewerImage, {
                        top: this._currentTop -delImageTop + &#39;px&#39;,
                        left: this._currentLeft -delImageLeft + &#39;px&#39;
                    });
                } else {
                    console.log(&#39;zoomout&#39;);
                    var top = 0;
                    var left = 0;
                    if (this._currentTop - delImageTop >= 0) {
                        top = 0;
                    } else if (this._currentTop - delImageTop +
                        this._minImageHeight + delImageHeight <=
                        this.idealHeight) {
                        top = this.idealHeight - this._minImageHeight - delImageHeight;
                    } else {
                        top = this._currentTop - delImageTop;
                    }
                    console.log(this._currentLeft, delImageLeft);
                    if (this._currentLeft - delImageLeft >= 0) {
                        left = 0;
                    } else if (this._currentLeft - delImageLeft +
                        this._minImageWidth + delImageWidth <=
                        this.idealWidth) {
                        left =this.idealWidth - this._minImageWidth - delImageWidth;
                    } else {
                        left = this._currentLeft - delImageLeft;
                    }

                    html.setStyle(this.baseImage, {
                        top: top + &#39;px&#39;,
                        left: left + &#39;px&#39;
                    });
                    html.setStyle(this.viewerImage, {
                        top: top + &#39;px&#39;,
                        left: left + &#39;px&#39;
                    });
                }
登录后复制

以上便是客户端的实现思路。全部代码,浏览器支持:现代浏览器和ie9+,稍后会将ie8也支持上。

服务器端使用nodejs+express框架,主要代码如下:

/**********
body: {
  imageString: base64 code
  maxSize: w,h
  cropOptions: w,h,t,l
}
************/
exports.cropImage = function(req, res) {
  var base64Img = req.body.imageString;
  if(!/^data:image\/.*;base64,/.test(base64Img)){
    res.send({
      success: false,
      message: &#39;Bad base64 code format&#39;
    });
  }
  var fileFormat = base64Img.match(/^data:image\/(.*);base64,/)[1];
  var base64Data = base64Img.replace(/^data:image\/.*;base64,/, "");
  var maxSize = req.body.maxSize;
  maxSize = maxSize.split(&#39;,&#39;);
  var cropOptions = req.body.cropOptions;
  cropOptions = cropOptions.split(&#39;,&#39;);

  try{
    var buf = new Buffer(base64Data, &#39;base64&#39;);
    var jimp = new Jimp(buf, &#39;image/&#39; + fileFormat, function() {
      var maxW = parseInt(maxSize[0], 10);
      var maxH = parseInt(maxSize[1], 10);
      var cropW = parseInt(cropOptions[0], 10);
      var cropH = parseInt(cropOptions[1], 10);
      var cropT = parseInt(cropOptions[2], 10);
      var cropL = parseInt(cropOptions[3], 10);
      this.resize(maxW, maxH)
      .crop(cropT, cropL, cropW, cropH);
    });

    jimp.getBuffer(&#39;image/&#39; + fileFormat, function(b) {
      var base64String = "data:image/" + fileFormat + ";base64," + b.toString(&#39;base64&#39;);
      res.send({
        success: true,
        source: base64String
      });
    });
  }catch(err) {
    logger.error(err);
    res.send({
      success: false,
      message: &#39;unable to complete operations&#39;
    });
  }
};
登录后复制


以上是JavaScript图片裁剪的无变形的示例代码分享(图)的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

如何使用WebSocket和JavaScript实现在线语音识别系统 如何使用WebSocket和JavaScript实现在线语音识别系统 Dec 17, 2023 pm 02:54 PM

如何使用WebSocket和JavaScript实现在线语音识别系统引言:随着科技的不断发展,语音识别技术已经成为了人工智能领域的重要组成部分。而基于WebSocket和JavaScript实现的在线语音识别系统,具备了低延迟、实时性和跨平台的特点,成为了一种被广泛应用的解决方案。本文将介绍如何使用WebSocket和JavaScript来实现在线语音识别系

WebSocket与JavaScript:实现实时监控系统的关键技术 WebSocket与JavaScript:实现实时监控系统的关键技术 Dec 17, 2023 pm 05:30 PM

WebSocket与JavaScript:实现实时监控系统的关键技术引言:随着互联网技术的快速发展,实时监控系统在各个领域中得到了广泛的应用。而实现实时监控的关键技术之一就是WebSocket与JavaScript的结合使用。本文将介绍WebSocket与JavaScript在实时监控系统中的应用,并给出代码示例,详细解释其实现原理。一、WebSocket技

如何利用JavaScript和WebSocket实现实时在线点餐系统 如何利用JavaScript和WebSocket实现实时在线点餐系统 Dec 17, 2023 pm 12:09 PM

如何利用JavaScript和WebSocket实现实时在线点餐系统介绍:随着互联网的普及和技术的进步,越来越多的餐厅开始提供在线点餐服务。为了实现实时在线点餐系统,我们可以利用JavaScript和WebSocket技术。WebSocket是一种基于TCP协议的全双工通信协议,可以实现客户端与服务器的实时双向通信。在实时在线点餐系统中,当用户选择菜品并下单

如何使用WebSocket和JavaScript实现在线预约系统 如何使用WebSocket和JavaScript实现在线预约系统 Dec 17, 2023 am 09:39 AM

如何使用WebSocket和JavaScript实现在线预约系统在当今数字化的时代,越来越多的业务和服务都需要提供在线预约功能。而实现一个高效、实时的在线预约系统是至关重要的。本文将介绍如何使用WebSocket和JavaScript来实现一个在线预约系统,并提供具体的代码示例。一、什么是WebSocketWebSocket是一种在单个TCP连接上进行全双工

JavaScript和WebSocket:打造高效的实时天气预报系统 JavaScript和WebSocket:打造高效的实时天气预报系统 Dec 17, 2023 pm 05:13 PM

JavaScript和WebSocket:打造高效的实时天气预报系统引言:如今,天气预报的准确性对于日常生活以及决策制定具有重要意义。随着技术的发展,我们可以通过实时获取天气数据来提供更准确可靠的天气预报。在本文中,我们将学习如何使用JavaScript和WebSocket技术,来构建一个高效的实时天气预报系统。本文将通过具体的代码示例来展示实现的过程。We

简易JavaScript教程:获取HTTP状态码的方法 简易JavaScript教程:获取HTTP状态码的方法 Jan 05, 2024 pm 06:08 PM

JavaScript教程:如何获取HTTP状态码,需要具体代码示例前言:在Web开发中,经常会涉及到与服务器进行数据交互的场景。在与服务器进行通信时,我们经常需要获取返回的HTTP状态码来判断操作是否成功,根据不同的状态码来进行相应的处理。本篇文章将教你如何使用JavaScript获取HTTP状态码,并提供一些实用的代码示例。使用XMLHttpRequest

javascript中如何使用insertBefore javascript中如何使用insertBefore Nov 24, 2023 am 11:56 AM

用法:在JavaScript中,insertBefore()方法用于在DOM树中插入一个新的节点。这个方法需要两个参数:要插入的新节点和参考节点(即新节点将要被插入的位置的节点)。

JavaScript和WebSocket:打造高效的实时图像处理系统 JavaScript和WebSocket:打造高效的实时图像处理系统 Dec 17, 2023 am 08:41 AM

JavaScript是一种广泛应用于Web开发的编程语言,而WebSocket则是一种用于实时通信的网络协议。结合二者的强大功能,我们可以打造一个高效的实时图像处理系统。本文将介绍如何利用JavaScript和WebSocket来实现这个系统,并提供具体的代码示例。首先,我们需要明确实时图像处理系统的需求和目标。假设我们有一个摄像头设备,可以采集实时的图像数

See all articles