ホームページ  >  記事  >  ウェブフロントエンド  >  変形せずにJavaScript画像をトリミングするためのサンプルコード共有(写真)

変形せずにJavaScript画像をトリミングするためのサンプルコード共有(写真)

黄舟
黄舟オリジナル
2017-03-13 16:51:141715ブラウズ

最近、Web サイトの画像トリミング効果をたくさん閲覧しました。そのほとんどの方法は、下の図に示すとおりです (スクリプト ホームから画像を借用)。トリミング ボックスのサイズを変更して、適切な位置を選択します。

ただし、この記事では別の切り抜き方法を紹介します。切り抜きボックスは開発者によって決定され、画像のサイズは画像の拡大縮小とドラッグによって適切な位置を選択します。右上の図に示すように、画像のアスペクト比は常に に維持されます。

この方法には主に次の利点があります:

  1. クロップフレームの幅と高さは実際のアスペクト比と一致し、画像の変形の問題を防ぎます

  2. は画像の表示サイズを制限しません、画像の元の比率、元のサイズをズームによって取得できることを確認します

  3. は、高解像度画像のごく一部をインターセプトするなど、ローカルのトリミングにより適しています。画像を拡大してドラッグするだけで済みます。他の方法では、トリミング フレームを非常に小さいサイズに調整する必要があり、ユーザーの操作には適していません

そうは言っても、欠点についてもお話ししておかなければなりません。一桁増加します。 。 。 。

主なアイデアは、2 つの写真を使用し、絶対に配置し、1 つをトリミング ボックスの内側に、もう 1 つをトリミング ボックスの外側に配置し、透明効果を設定することです。トリミング ボックス オーバーフロー は非表示になり、両方とも維持されます。常に写真を完全に同期します。

<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 イベント をバインドします。マウスがワークスペースを離れた後も、ドラッグまたはズームを続けることができます。次の主な作業は、スタートアップと _init 関数 です。 Dojo に詳しくない友人は、起動前に postCreate が実行されることだけを知っておく必要があります。

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. 翻訳のプロセスは比較的単純で、移動中のマウスの相対的な位置の変化を記録し、画像の左上隅の左と上を継続的に変更するだけです。 ragstart および selectstart イベント、preventDefault は、要素が選択されて青色に変わるのを防ぎます。
そうです

スケーリングの主な原則は、

スケーリングの前後でクロップフレームの中心点の相対位置を変更しないように保つことです

拡大縮小された元のトリミングフレームの中心点を元の位置に戻すには、画像サイズの変化と画像の左上隅の移動という 2 つの中間値を計算する必要があります。画像。

_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;
                }
            },

ここで、 _zoomRatio = _maxImageWidth / _minImageWidth; _maxImageWidth は画像の元のサイズ、_minImageWidth は画像をトリミング ボックスに近づける最小幅です。

leftPercentage は、スライド バーに対するスライド ボタンの変位パーセンテージです。

_currentLeft と _currentTop は、このズーム前のトリミング フレームを基準とした画像の絶対位置 (position:absolute) です。

_currentImageWidth、_currentImageHeight は、このスケーリング前の画像のサイズです。

あとは、クロップ ボックスに空白が表示されないようにするだけです。画像を拡大して、クロップ ボックスの境界線に合わせて画像をドラッグすると、画像が縮小され、空白が表示されます。クロップボックスに表示されます。このような事態を防ぐために、私たちも相応の対策を講じる必要があります。

画像の左上の境界線がクロップボックスの左上の境界線と一致する場合、どのようにズームアウトしても画像の左と上は常にゼロになり、画像のサイズのみが変化します。

画像の右下の境界線がクロップボックスの右下の境界線と一致する場合、画像のサイズとクロップボックスのサイズに基づいて適切な左と上を計算できます

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);

上記はクライアントの実装アイデア。すべてのコード、ブラウザのサポート: 最新のブラウザと 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 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。