Q: Rotate image by degree and crop I try rotate image by degree and crop part of it and than show in canvas, but image is cropped more than I did. I use this https://github.com/BossBele/cropzee
Let's see snippet (on full page) and try upload some image which will rotate and crop. Result is not related to modal.


jQuery(document).ready(function ($) {
    jQuery("#crop").cropzee();
});

var defaultWidth = 210;

// used jQuery.fn.extend() method to provide new methods that can be chained to the jQuery() function
// in our case - $(element).cropzee()
jQuery.fn.extend({
    cropzee: function (options = {
    // croppr.js options
    // see https://jamesooi.design/Croppr.js/
    aspectRatio: null,
            maxSize: null,
            minSize: null,
            startSize: [100, 100, '%'],
            onCropStart: null,
            onCropMove: null,
            onCropEnd: null,
            onInitialize: null,
            // lightmodal options
            // see https://hunzaboy.github.io/Light-Modal/#
            modalAnimation: '',
            // cropzee options
            allowedInputs: ['jpg'], // input extensions supported
            imageExtension: 'image/jpeg', // cropped image/blob file-type 'image/jpeg' | 'image/png' | any other supported by browser
            returnImageMode: 'data-url', // image data mode, 'blob' for blob object or 'data-url' for dataURL
    }) {
        if (options.aspectRatio <= 0) {
            options.aspectRatio = null;
        }
        if (!options.allowedInputs) {
            options.allowedInputs = ['jpg', 'jpeg'];
        }
        if (!options.imageExtension) {
            options.imageExtension = 'image/jpeg';
        }
        if (!options.returnImageMode) {
            options.returnImageMode = 'data-url';
        }
        // function to reset input (value) of input, taking in input id
        // resets input value of cropzee input type=file so that same file can be selected twice
        function resetFileInput(id) {
            $('#' + id).val(null);
        }
        window.cropzeeGetImage = function (id) {
            return cropzeeReturnImage[id];
        };
        window.cropzeeRotateImage = function (degree) {
            var image = document.getElementsByClassName('croppr-image')[0];
            var imageClipped = document.getElementsByClassName('croppr-imageClipped')[0];
            var rotateValue = document.getElementById('upload-photo-image-rotate-value');
            var rotateValueText = document.getElementById('upload-photo-image-rotate-value-text');

            image.style.transform = 'rotate(' + degree + 'deg)';
            imageClipped.style.transform = 'rotate(' + degree + 'deg)';
            rotateValue.value = degree;
            rotateValueText.value = degree.replace('.', ',') + '°';
        };
        // function to crop the modal-image and display it on the hidden canvas and other dynamic canvases (previewers)
        window.cropzeeCreateImage = function (id) {
            var rotateValueEl = document.getElementById('upload-photo-image-rotate-value');
            var rotateValueRad = rotateValueEl.value * (Math.PI / 180);
            // get croppr.js dimensions
            var dimensions = cropzeeCroppr.getValue();
            // get hidden canvas and draw cropped image onto it
            var img = document.getElementsByClassName('croppr-image')[0];
            //var width = 210;
            //var ratio = img.height / img.width;
            //var height = width * ratio;
            var canvas = document.getElementById('cropzee-hidden-canvas');
            var x = dimensions.width / 2;
            var y = dimensions.height / 2;
            var ctx = canvas.getContext('2d');

            ctx.canvas.width = dimensions.width;
            ctx.canvas.height = dimensions.height;

            ctx.save();

            ctx.translate(dimensions.width / 2, dimensions.height / 2);
            ctx.rotate(rotateValueRad);

            ctx.translate(-dimensions.width / 2, -dimensions.height / 2);
            ctx.drawImage(img, dimensions.x, dimensions.y, dimensions.width, dimensions.height, 0, 0, dimensions.width, dimensions.height);

            ctx.restore();
            // store image data as blob or dataURL for retrieval
            if (options.returnImageMode == 'blob') {
                canvas.toBlob(function (blob) {
                    window.cropzeeReturnImage = [];
                    cropzeeReturnImage[id] = blob;
                }, options.imageExtension);
            } else {
                window.cropzeeReturnImage = [];
                cropzeeReturnImage[id] = canvas.toDataURL(options.imageExtension);
            }

            // cropping finished, close modal
            //closeModal();
        };
        // function to initialize croppr.js on the image inside modal
        // returnMode option is not supported in cropzee
        // see https://jamesooi.design/Croppr.js/
        function cropzeeTriggerCroppr() {
            window.cropzeeCroppr = new Croppr('#cropzee-modal-image', {
                aspectRatio: options.aspectRatio,
                maxSize: options.maxSize,
                minSize: options.minSize,
                startSize: options.startSize,
                onCropStart: options.onCropStart,
                onCropMove: options.onCropMove,
                onCropEnd: options.onCropEnd,
                onInitialize: options.onInitialize
            });
        }
        // function to trigger modal and pass image data to display in the modal
        // function takes in input id and image (to be cropped) data
        function cropzeeTriggerModal(id, src) {
            // take in animation option and add 'animated' before it
            var animation = options.modalAnimation;
            if (animation) {
                if (animation.indexOf('animated') == -1) {
                    animation = 'animated ' + animation;
                }
            }
            // modal element with dynamic image data, dynamic animation class as supported by animate.css and dynamic input id
            // lightmodal see https://hunzaboy.github.io/Light-Modal/#
            var lightmodalHTML =
                    '<div class="light-modal" id="cropzee-modal" role="dialog" aria-labelledby="light-modal-label" aria-hidden="false" data-lightmodal="close">'
                    + '<div class="light-modal-content ' + animation + '">'
                    + '<!-- light modal header -->'
                    + '<!-- <div class="light-modal-header">'
                    + '<h3 class="light-modal-heading">Cropzee</h3>'
                    + '<a href="#" class="light-modal-close-icon" aria-label="close">&times;</a>'
                    + '</div> -->'
                    + '<!-- light modal body -->'
                    + '<div class="light-modal-body" style="max-height: 500px;">'
                    + '<img id="cropzee-modal-image" src="' + src + '" style="display:none;">'
                    + '</div>'
                    + '<!-- light modal footer -->'
                    + '<div class="light-modal-footer" style="justify-content: space-between;">'
                    + '<div onclick="closeModal()" class="button btn" aria-label="close">Cancel</div>'
                    + '<input id="upload-photo-image-rotate-value" type="hidden" name="rangeInputValue" value="0">'
                    + '<input id="upload-photo-image-rotate-value-text" class="image-rotate-value" type="text" name="rangeInputValueText" value="0°" readonly>'
                    + '<input oninput="cropzeeRotateImage(this.value)" onchange="cropzeeRotateImage(this.value)" id="upload-photo-image-rotate" class="image-rotate" type="range" name="rangeInput" step="1" min="-90" max="90" value="0">'
                    + '<div onclick="cropzeeCreateImage(`' + id + '`);" class="button btn">Done</div>'
                    + '</div>'
                    + '</div>'
                    + '<canvas style="position: absolute; top: -99999px; left: -99999px;" id="cropzee-hidden-canvas"></canvas>'
                    + '<a style="display:none;" id="cropzee-link"></a>'
                    + '</div>';
            // modal element is appended to body
            $("body").append(lightmodalHTML);
            // after which the inserted image is drawn onto the hidden canvas within the modal
            setTimeout(function () {
                var canvas = document.getElementById('cropzee-hidden-canvas');
                var ctx = canvas.getContext('2d');
                ctx.canvas.width = cropzeeCanvasWidth;
                ctx.canvas.height = cropzeeCanvasHeight;
                var img = new Image();
                img.src = src;
                ctx.drawImage(img, 0, 0, cropzeeCanvasWidth, cropzeeCanvasHeight);
                setTimeout(function () {
                    // the css-only modal is called via href see https://hunzaboy.github.io/Light-Modal/#
                    window.location = window.location.pathname + "#cropzee-modal";
                    // function to trigger croppr.js on picture in modal
                    cropzeeTriggerCroppr();
                }, 50);
            }, 50);
        }
        // function to capture input and insert it into various elements for previewing and display
        function cropzeeReadURL(input, id) {
            if (input.files && input.files[0]) {
                var reader = new FileReader();

                reader.onload = function (e) {
                    window.cropzeePreviewersLength = $('[data-cropzee="' + id + '"]').length;
                    window.cropzeePreviewCanvas = [];
                    window.cropzeePreviewCanvasContext = [];
                    if (cropzeePreviewersLength) {
                        for (let i = 0; i < cropzeePreviewersLength; i++) {
                            var img = new Image();
                            img.onload = function () {
                                var ratio = img.height / img.width;
                                img.width = 210;
                                cropzeePreviewCanvas[i] = $('[data-cropzee="' + id + '"]')[i];
                                cropzeePreviewCanvasContext[i] = cropzeePreviewCanvas[i].getContext('2d');
                                cropzeePreviewCanvasContext[i].canvas.width = $(cropzeePreviewCanvas[i]).width() || (defaultWidth + increment);
                                window.cropzeeCanvasWidth = $(cropzeePreviewCanvas[i]).width() || (defaultWidth + increment);
                                cropzeePreviewCanvasContext[i].canvas.height = cropzeePreviewCanvasContext[i].canvas.width * ratio;
                                window.cropzeeCanvasHeight = $(cropzeePreviewCanvas[i]).height() || 'auto';
                                cropzeePreviewCanvasContext[i].drawImage(img, 0, 0, cropzeeCanvasWidth, cropzeeCanvasHeight);
                            };
                            img.src = e.target.result;
                        }
                    }
                    cropzeeTriggerModal(id, e.target.result);
                };
                reader.readAsDataURL(input.files[0]);
            }
        }
        // function to close modal when user clicks outside modal
        $(document).click(function (e) {
            if ($(e.target).is('#cropzee-modal')) {
                closeModal();
            }
        });
        // function that is called first, when input is triggered
        // it resets input value to enable the reloading of the same image just in case
        $(this).click(function () {
            var cropzeeInputId = $(this).attr('id');
            resetFileInput(cropzeeInputId);
            // when image is selected, the image-previewers are transformed to canvases
            // then the input data is passed to be read for previewing
            $(this).one("change", function () {
                var ext = $('#' + cropzeeInputId).val().split('.').pop().toLowerCase();
                if ($.inArray(ext, options.allowedInputs) == -1) {
                    alert('invalid extension! Please check your input file and try again.');
                    resetFileInput(cropzeeInputId);
                } else {
                    var previewerId = $('[data-cropzee="' + cropzeeInputId + '"]').attr("id");
                    var previewerClass = $('[data-cropzee="' + cropzeeInputId + '"]').attr("class");
                    $('[data-cropzee="' + cropzeeInputId + '"]').replaceWith('<canvas id="' + previewerId + '" class="' + previewerClass + '" data-cropzee="' + cropzeeInputId + '"></canvas>');
                    // input data is passed to be read for previewing
                    // function takes in input object and its id
                    cropzeeReadURL(this, cropzeeInputId);
                }
            });
        });
    }
});
// function to close modal
function closeModal() {
    $('#cropzee-modal').remove();
    window.location = window.location.pathname + '#';
}
/*!
   light-modal v1.1.0: A new lightweight css modal.
   (c) 2017 
   MIT License
   git+https://github.com/hunzaboy/Light-Modal.git
*/
.button {
  display: block;
  cursor: pointer;
  padding: 10px 20px;
  margin: 10px 0;
  background: #4C7CC4;
  clear: both;
  color: #FFF;
}
.light-modal {
    display: none;
    position: fixed;
    background: transparent;
    top: 0;
    bottom: 0;
    left: 0;
    -ms-flex-align: center;
    align-items: center;
    -ms-flex-pack: center;
    justify-content: center;
    right: 0;
    z-index: 9000;
    transition: background 1s;
    font-size: 16px;
    visibility: hidden;
}

.light-modal-content {
    background: #fff;
    color: #fff;
    width: 30vw;
    border-radius: .2em;
    position: relative;
    max-height: calc(100vh - 150px);
    line-height: 1.4;
    display: -ms-flexbox;
    display: flex;
    -ms-flex-direction: column;
    flex-direction: column;
min-height: 200px;
}

.light-modal-content img {
    max-width: 100%;
    border-radius: .2em;
}

.light-modal-content.large-content {
    width: 50vw;
}

.light-modal-header {
    padding: 20px 20px 20px 20px;
    background: rgba(0, 0, 0, 0.2);
    display: -ms-flexbox;
    display: flex;
    -ms-flex-pack: justify;
    justify-content: space-between;
    -ms-flex-align: center;
    align-items: center;
}

.light-modal-heading {
    margin: 0;
    font-size: 1.5em;
}

.light-modal-heading + .light-modal-close-icon {
    position: static;
}

.light-modal-body {
    padding: 20px;
    overflow: auto;
    max-height: 450px;
}

.light-modal-footer {
    position: relative;
    padding: 20px 20px 20px 20px;
    text-align: right;
    display: -ms-flexbox;
    display: flex;
    -ms-flex-align: center;
    align-items: center;
}

.light-modal-footer .image-rotate-value {
    position: absolute;
    border: 0;
    text-align: center;
    top: 0;
    width: calc(100% - 40px);
}

.light-modal-footer .image-rotate {
    margin: 0 10px;
}

.light-modal-close-icon, .light-modal-close-btn {
    text-decoration: none;
    color: #fff;
    padding: 5px 10px;
    border-radius: .2em;
    background: #4C7CC4;
    font-size: 1.5em;
    line-height: 1;
    transition: background .2s ease-in-out;
}

.light-modal-close-icon:hover, .light-modal-close-btn:hover {
    background: #46BF46;
}

.light-modal-close-icon {
    position: absolute;
    top: -15px;
    right: -15px;
}

.light-modal-close-btn {
    font-size: 1em;
}

.light-modal-caption {
    position: absolute;
    left: 50%;
    -ms-transform: translateX(-50%);
    transform: translateX(-50%);
    top: 100%;
    padding: 10px 0;
    background: rgba(0, 0, 0, 0.2);
    border-radius: .2em;
    width: 100%;
    text-align: center;
    margin-top: 5px;
}

.light-modal:target {
    background: rgba(0, 0, 0, 0.5);
    display: -ms-flexbox;
    display: flex;
    visibility: visible;
}

.light-modal-navigation .navigation-next,
.light-modal-navigation .navigation-prev {
    width: 32px;
    height: 32px;
    border-color: #fff;
    transition: border-color .2s;
}

.light-modal-navigation .navigation-next:hover,
.light-modal-navigation .navigation-prev:hover {
    border-color: rgba(255, 255, 255, 0.7);
}

.light-modal-navigation .navigation-next {
    position: absolute;
    right: -50px;
    top: 50%;
    border-bottom: 1px solid;
    border-left: 1px solid;
    -ms-transform: rotate(-135deg);
    transform: rotate(-135deg);
}

.light-modal-navigation .navigation-prev {
    position: absolute;
    left: -50px;
    top: 50%;
    border-bottom: 1px solid;
    border-left: 1px solid;
    -ms-transform: rotate(45deg);
    transform: rotate(45deg);
}

.image-previewer {
    min-height: 10px;
    width: 270px;
}

@media (max-width: 480px) {
    .light-modal-navigation .navigation-next {
        right: 5px;
    }
    .light-modal-navigation .navigation-prev {
        left: 5px;
    }
}

@keyframes basic {
    0% {
        opacity: 0;
    }
    100% {
        opacity: 1;
    }
}

.basic {
    animation-name: basic;
}

@media (max-width: 767px) {
    .light-modal {
        font-size: 14px;
    }
    .light-modal:target .light-modal-content {
        width: 70vw;
    }
}

@supports (display: flex) {
    @media (max-width: 767px) {
        .light-modal:target .light-modal-content {
            width: 70vw;
        }
    }
}
<html>
<head>
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<link href="https://cdn.jsdelivr.net/gh/daneden/animate.css@latest/animate.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/gh/eligrey/canvas-toBlob.js@latest/canvas-toBlob.min.js" defer></script>
<script src="https://cdn.jsdelivr.net/gh/eligrey/FileSaver.js@latest/dist/FileSaver.min.js" defer></script>
<link href="https://cdn.jsdelivr.net/gh/jamesssooi/Croppr.js@latest/dist/croppr.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/gh/jamesssooi/Croppr.js@latest/dist/croppr.min.js"></script>
</head>
<body>
<form action="" method="post">
<div id="cropzee-hidden-canvas" class="image-previewer" data-cropzee="crop"></div>
<input id="crop" type="file" name="filename">
<input type="submit" name="upload" value="upload">
</form>
</body>
</html>


That worked, but image is cropped (bottom and right side) more than should be.
New snippet with cropper:


        
            window.addEventListener('DOMContentLoaded', function () {
                    $("#crop").change(function () {
                        readURL(this);
                    });
            });
        
            function readURL(input) {
                if (input.files && input.files[0]) {
                    var reader = new FileReader();
        
                    reader.onload = function (e) {
                        $('.cropper-container').remove();
                        $('#upload-photo-image-rotate').val(0);
                        $('#upload-photo-image-rotate-value').val(0);
                        $('#upload-photo-image-rotate-value-text').val('0°');
                        
                        var cropContainer = $('#crop-container');
                        var button = $('#done');
        
                        var image = new Image();
                        image.id = 'upload-preview-image';
                        image.src = e.target.result;
                        image.onload = function () {
                            $(cropContainer).html(image);
                        };
        
                        var cropBoxData;
                        var canvasData;
                        var cropper;
        
                        var modalImage = document.getElementById('modal-upload-preview-image');
                        modalImage.src = image.src;
                        cropper = new Cropper(modalImage, {
                            autoCropArea: 1,
                            minCropBoxHeight: $(cropContainer).data('min-height'),
                            minCropBoxWidth: $(cropContainer).data('min-width'),
                            scalable: false,
                            ready: function () {                               cropper.setCropBoxData(cropBoxData).setCanvasData(canvasData);
                            }
                        });
        
                        $('#modal-upload-preview').on('shown.bs.modal', function () {
                            modalImage.onload = function () {
                                $(document).on("input", "#upload-photo-image-rotate", function () {
                                    rotate(cropper, $(this).val());
                                });
        
                                $(document).on("change", "#upload-photo-image-rotate", function () {
                                    rotate(cropper, $(this).val());
                                });
        
                                $(button).on('click', function () {
                                    var canvas = cropper.getCroppedCanvas();
                                    var data = cropper.getData();
                                    $(cropContainer).html(canvas);
        
                                    $('#photoX').val(data.x);
                                    $('#photoY').val(data.y);
                                    $('#photoWidth').val(data.width);
                                    $('#photoHeight').val(data.height);
                                    $('#photoDegree').val(data.rotate);
                                    $('#modal-upload-preview').modal('hide');
    $('#data-viewer').text(JSON.stringify(cropper.getData()));
                                });
                            };
                        }).on('hidden.bs.modal', function () {
                            cropBoxData = cropper.getCropBoxData();
                            canvasData = cropper.getCanvasData();
                            cropper.destroy();
                        }).modal('show');
                    };
        
                    reader.readAsDataURL(input.files[0]);
                }
            }
        
            function rotate(cropper, degree) {
                var rotateValue = document.getElementById('upload-photo-image-rotate-value');
                var rotateValueText = document.getElementById('upload-photo-image-rotate-value-text');
                rotateValue.value = degree;
                rotateValueText.value = degree.replace('.', ',') + '°';
                cropper.rotateTo(degree);
            }
        
        
        
            #upload-preview-image {
                max-width: 100%;
            }
            #modal-upload-preview .cropper-bg {
                background: #000;
            }
            #modal-upload-preview .image-rotate-value {
                border: 0;
                text-align: center;
                width: 100%;
            }
            #modal-upload-preview .image-rotate {
        
            }
        
        
        
            <html>
            <head>
            <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
            <script type="text/javascript" defer="defer" src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.bundle.min.js"></script>
            <script type="text/javascript" defer="defer" src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.6/cropper.min.js"></script>
            <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" media="screen" rel="stylesheet" type="text/css" >
        <link href="https://fengyuanchen.github.io/cropperjs/css/cropper.css" media="screen" rel="stylesheet" type="text/css" >
            </head>
            <body>
            <div id="crop-container" data-min-height="185" data-min-width="210">
            </div>
            <form action="" method="post">
            <input id="crop" type="file" name="filename">
<input type="hidden" name="photoX" value="0" id="photoX">
<input type="hidden" name="photoY" value="0" id="photoY">
<input type="hidden" name="photoWidth" value="0" id="photoWidth">
<input type="hidden" name="photoHeight" value="0" id="photoHeight">
<input type="hidden" name="photoDegree" value="0" id="photoDegree">
<div id="data-viewer"></div>
            <input type="submit" name="upload" value="upload">
            </form>
            <div class="modal" id="modal-upload-preview" tabindex="-1" role="dialog" aria-labelledby="modalLabel" aria-hidden="true">
                <div class="modal-dialog" role="document">
                    <div class="modal-content">
                        <div class="modal-header">
                            <h5 class="modal-title" id="modalLabel">Oříznout a natočit obrázek</h5>
                            <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                                <span aria-hidden="true">&times;</span>
                            </button>
                        </div>
                        <div class="modal-body">
                            <div class="img-container" style="max-width: 100%;">
                                <img id="modal-upload-preview-image" src="">
                                <div style="position:relative;">
                                    <input id="upload-photo-image-rotate-value" type="hidden" name="rangeInputValue" value="0">
                                    <input id="upload-photo-image-rotate-value-text" class="image-rotate-value" type="text" name="rangeInputValueText" value="0°" readonly>
                                    <input id="upload-photo-image-rotate" class="image-rotate" type="range" name="rangeInput" step="0.1" min="-2" max="2" value="0">
                                </div>
                            </div>
                        </div>
                        <div class="modal-footer">
                            <button id="done" class="button btn pull-right" type="button">Oříznout</button>
                        </div>
                    </div>
                </div>
            </div>
            </body>
            </html>





