Масштабирование при касании/щипке на элементе div с использованием преобразования css, перевода и масштабирования с помощью HammerJS

У меня есть контейнер, который может содержать другие элементы div, p, image и другие элементы html.

<div id="mycontainer">
   <div>This is a child div positioned on top middle</div>
   <p>This is a paragraph position on the middle on container div</p>
   <!-- image positioned at the bottom -->
    <img src="image.jpg"></img>
</div>

Когда я щелкаю (с помощью мыши) или касаюсь/зажимаю часть div #mycontainer, я хочу увеличить div #mycontainer с его содержимым (div,p,img) относительно положения, в котором пользователь щелкнул/нажал/ущипнул

Как мне сделать это в javascript/jquery, используя css transform translate() и scale()?

В настоящее время я знаю, как его установить, это будет так:

$('#mycontainer')
.css('-moz-transform', 'scale(1) translate(0px, 0px)')
.css('-webkit-transform', 'scale(1) translate(0px, 0px)')
.css('-o-transform', 'scale(1) translate(0px, 0px)')
.css('transform', 'scale(1) translate(0px, 0px)');

но я не знаю, как вычислить значения в translate(), когда пользователь нажимает/зажимает

ОБНОВЛЕНИЕ:

Ответы здесь кажутся похожими, но они используют transform-origin Увеличить на точку (с использованием масштабирования и перевода)

Я не хочу использовать преобразование-происхождение, я просто хочу использовать преобразование: перевод () и преобразование: масштаб ()

Я хочу, чтобы эта реализация была реализована в демоверсии домашней страницы http://hammerjs.github.io/. Попробуйте зажать белую коробку, и она будет масштабироваться и вращаться. Мне не нужен код для поворота, просто масштабируйте

Пожалуйста помоги!


person Bogz    schedule 11.08.2014    source источник


Ответы (1)


сложная проблема с просмотром / масштабированием и панорамированием изображения, верно? :)

Наконец-то мне удалось откалибровать алгоритм масштабирования, поэтому я хочу поделиться им с сообществом. Я создал класс просмотра для взаимодействия с базовым изображением. Одним из важных моментов в моем решении является то, что оно не изменяет исходное значение преобразования по умолчанию, что может быть полезно для некоторых других преобразований.

Вы можете использовать щелчок, чтобы увеличить / Ctrl + щелчок, чтобы уменьшить масштаб, или ущипнуть в ущипнуть (использует Hammer JS). Внимание, сенсорные события не включены по умолчанию в Firefox.

Извините, я знаю, что он использует Hammer и самодельные классы Transform & Point, но, пожалуйста, сосредоточьтесь на методе zoomTo, который не зависит от фреймворка и является основной точкой этой проблемы с масштабированием.

(Вы найдете версию TypeScript ниже, если хотите)

Попробуйте это в этом фрагменте

// LOAD VIEWER
window.onload = function() {
var v = new UI.Viewer(document.getElementById('viewer'));
            v.setViewPortSize({width: 900, height: 600});
            v.setSource('https://upload.wikimedia.org/wikipedia/commons/d/d9/Big_Bear_Valley,_California.jpg');
}


var Point = (function () {
    function Point(x, y) {
        this.x = x;
        this.y = y;
    }
    Point.prototype.toString = function () {
        return '(' + this.x + ';' + this.y + ')';
    };
    return Point;
})();
var Transform = (function () {
    function Transform() {
        this.translate = new Point(0, 0);
        this.scale = 1;
        this.angle = 0;
    }
    return Transform;
})();
var UI;
(function (UI) {
    var Viewer = (function () {
        function Viewer(viewer) {
            this.ticking = false;
            console.info("viewer browser on: " + viewer);
            this.viewer = viewer;
            this.viewer.style.position = 'relative';
            this.viewer.style.overflow = 'hidden';
            this.viewer.style.touchAction = 'none';
            this.viewer.style.backgroundColor = '#000000';
            this.viewer.style['-webkit-user-select'] = 'none';
            this.viewer.style['-webkit-user-drag'] = 'none';
            this.viewer.style['-webkit-tap-highlight-color'] = 'rgba(0, 0, 0, 0)';
            this.viewerContent = this.viewer.querySelector(".image");
            if (this.viewerContent == null) {
                this.viewerContent = document.createElement('img');
                this.viewerContent.className = 'image';
                this.viewer.appendChild(this.viewerContent);
            }
            this.viewerContent.style.position = 'absolute';
            this.viewerContent.style.transition = 'transform 100ms linear';
            console.info("image width = " + this.viewer.clientWidth + "x" + this.viewer.clientHeight);
            this.transform = new Transform();
            this.initializeHammerEvents();
            console.info("viewer controller constructed: " + this.transform);
            this.setViewPortSize({ width: this.viewer.clientWidth, height: this.viewer.clientHeight });
        }
        Viewer.prototype.initializeHammerEvents = function () {
            var _this = this;
            this.gestureManager = new Hammer.Manager(this.viewer, {
                touchAction: 'pan-x pan-y'
            });
            this.gestureManager.add(new Hammer.Pinch({
                threshold: 0
            }));
            this.gestureManager.on("pinchstart pinchmove", function (event) { _this.onPinch(event); });
            this.viewerContent.addEventListener("click", function (event) {
                _this.onImageClick(event);
            });
        };
        Viewer.prototype.enableGestures = function () {
            this.initializeHammerEvents();
            this.viewer.style.pointerEvents = 'auto';
        };
        Viewer.prototype.disableGestures = function () {
            this.viewer.style.pointerEvents = 'none';
            this.gestureManager.off('panstart panmove rotatestart rotatemove pinchstart pinchmove pinchend rotateend press doubletap');
        };
        Viewer.prototype.setViewPortSize = function (size) {
            this.viewer.style.width = size.width + 'px';
            this.viewer.style.height = size.height + 'px';
            this.adjustZoom();
        };
        Viewer.prototype.getViewPortSize = function () {
            return {
                width: this.viewer.clientWidth,
                height: this.viewer.clientHeight
            };
        };
        Viewer.prototype.getDocumentSize = function () {
            return {
                width: this.viewerContent.clientWidth,
                height: this.viewerContent.clientHeight
            };
        };
        Viewer.prototype.setSource = function (source) {
            var _this = this;
            this.viewerContent.src = source;
            this.viewerContent.onload = function () {
                console.info("image loaded");
                _this.adjustZoom();
            };
        };
        Viewer.prototype.adjustZoom = function () {
            var size = this.getViewPortSize();
            var documentSize = this.getDocumentSize();
            console.info("adjust zoom, documentSize: " + documentSize.width + "x" + documentSize.height);
            console.info("adjust zoom, viewPortSize: " + size.width + "x" + size.height);
            this.minScale = 100 / documentSize.width;
            console.info("minScale=" + this.minScale);
            var widthScale = size.width / documentSize.width;
            var heightScale = size.height / documentSize.height;
            var scale = Math.min(widthScale, heightScale);
            var left = (size.width - documentSize.width) / 2;
            var top = (size.height - documentSize.height) / 2;
            console.log("setting content to : left => " + left + "  , top => " + top, ", scale => ", scale);
            this.viewerContent.style.left = left + 'px';
            this.viewerContent.style.top = top + 'px';
            this.transform.scale = scale;
            this.updateElementTransform();
        };
        Viewer.prototype.onPinch = function (ev) {
            var pinchCenter = new Point(ev.center.x - this.viewer.offsetLeft, ev.center.y - this.viewer.offsetTop);
            console.info("pinch - center=" + pinchCenter + " scale=" + ev.scale);
            if (ev.type == 'pinchstart') {
                this.pinchInitialScale = this.transform.scale || 1;
            }
            var targetScale = this.pinchInitialScale * ev.scale;
            if (targetScale <= this.minScale) {
                targetScale = this.minScale;
            }
            if (Math.abs(this.transform.scale - this.minScale) < 1e-10
                && Math.abs(targetScale - this.minScale) < 1e-10) {
                console.debug('already at min scale');
                this.requestElementUpdate();
                return;
            }
            this.zoomTo(new Point(ev.center.x, ev.center.y), targetScale);
        };
        Viewer.prototype.onImageClick = function (event) {
            console.info("click");
            var zoomCenter = new Point(event.pageX - this.viewer.offsetLeft, event.pageY - this.viewer.offsetTop);
            var scaleFactor = event.shiftKey || event.ctrlKey ? 0.75 : 1.25;
            this.zoomTo(zoomCenter, scaleFactor * this.transform.scale);
        };
        Viewer.prototype.zoomTo = function (zoomCenter, newScale) {
            var viewPortSize = this.getViewPortSize();
            var viewPortCenter = new Point(viewPortSize.width / 2, viewPortSize.height / 2);
            var zoomRelativeCenter = new Point(zoomCenter.x - viewPortCenter.x, zoomCenter.y - viewPortCenter.y);
            console.debug('clicked at ' + zoomRelativeCenter + ' (relative to center)');
            var oldScale = this.transform.scale;
            // calculate translate difference 
            // 1. center on new coordinates
            var zoomDx = -(zoomRelativeCenter.x) / oldScale;
            var zoomDy = -(zoomRelativeCenter.y) / oldScale;
            // 2. translate from center to clicked point with new zoom
            zoomDx += (zoomRelativeCenter.x) / newScale;
            zoomDy += (zoomRelativeCenter.y) / newScale;
            console.debug('dx=' + zoomDx + ' dy=' + zoomDy + ' oldScale=' + oldScale);
            /// move to the difference
            this.transform.translate.x += zoomDx;
            this.transform.translate.y += zoomDy;
            this.transform.scale = newScale;
            console.debug("applied zoom: scale= " + this.transform.scale + ' translate=' + this.transform.translate);
            this.requestElementUpdate();
        };
        Viewer.prototype.requestElementUpdate = function () {
            var _this = this;
            if (!this.ticking) {
                window.requestAnimationFrame(function () { _this.updateElementTransform(); });
                this.ticking = true;
            }
        };
        Viewer.prototype.updateElementTransform = function () {
            var value = [
                'rotate(' + this.transform.angle + 'deg)',
                'scale(' + this.transform.scale + ', ' + this.transform.scale + ')',
                'translate3d(' + this.transform.translate.x + 'px, ' + this.transform.translate.y + 'px, 0px)',
            ];
            var stringValue = value.join(" ");
            console.debug("transform = " + stringValue);
            this.viewerContent.style.transform = stringValue;
            this.viewerContent.style.webkitTransform = stringValue;
            this.viewerContent.style.MozTransform = stringValue;
            this.viewerContent.style.msTransform = stringValue;
            this.viewerContent.style.OTransform = stringValue;
            this.ticking = false;
        };
        return Viewer;
    })();
    UI.Viewer = Viewer;
})(UI || (UI = {}));
<!DOCTYPE html>
<html lang="fr">
    <head>
        <link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">
    </head>

    <body>
<br />
<br />
<br />
        <div id="viewer">
        </div>

        <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.min.js"></script>

    </body>
</html>

Версия TypeScript

class Point {

    public x: number;
    public y: number;

    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }

    public toString(): string {
        return '(' + this.x + ';' + this.y + ')';
    }
}

interface Dimension {
    width: number;
    height: number;
}

class Transform {
    constructor() {
        this.translate = new Point(0, 0);
        this.scale = 1;
        this.angle = 0;
    }
    public translate: Point;
    public scale: number;
    public angle: number;
}

namespace UI {

    export class Viewer {

        private transform: Transform;
        private gestureManager: HammerManager;

        private viewer: HTMLDivElement;
        private viewerContent: HTMLImageElement;

        private ticking: boolean = false;

        private minScale: number;
        private pinchInitialScale: number;

        constructor(viewer: HTMLDivElement) {
            console.info("viewer browser on: " + viewer);

            this.viewer = viewer;
            this.viewer.style.position = 'relative';
            this.viewer.style.overflow = 'hidden';
            this.viewer.style.touchAction = 'none';
            this.viewer.style.backgroundColor = '#000000';
            this.viewer.style['-webkit-user-select'] = 'none';
            this.viewer.style['-webkit-user-drag'] = 'none';
            this.viewer.style['-webkit-tap-highlight-color'] = 'rgba(0, 0, 0, 0)';

            this.viewerContent = <HTMLImageElement>this.viewer.querySelector(".image");
            if (this.viewerContent == null) {
                this.viewerContent = document.createElement('img');
                this.viewerContent.className = 'image';
                this.viewer.appendChild(this.viewerContent);
            }
            this.viewerContent.style.position = 'absolute';
            this.viewerContent.style.transition = 'transform 100ms linear';
            console.info("image width = " + this.viewer.clientWidth + "x" + this.viewer.clientHeight);

            this.transform = new Transform();

            this.initializeHammerEvents();

            console.info("viewer controller constructed: " + this.transform);

            this.setViewPortSize({ width: this.viewer.clientWidth, height: this.viewer.clientHeight });
        }

        public initializeHammerEvents(): void {

            this.gestureManager = new Hammer.Manager(this.viewer, {
                touchAction: 'pan-x pan-y'
            });

            this.gestureManager.add(new Hammer.Pinch({
                threshold: 0
            }));

            this.gestureManager.on("pinchstart pinchmove", (event) => { this.onPinch(event); });

            this.viewerContent.addEventListener("click", (event: MouseEvent) => {
                this.onImageClick(event);
            });
        }

        private enableGestures(): void {
            this.initializeHammerEvents();
            this.viewer.style.pointerEvents = 'auto';
        }

        private disableGestures(): void {
            this.viewer.style.pointerEvents = 'none';
            this.gestureManager.off('panstart panmove rotatestart rotatemove pinchstart pinchmove pinchend rotateend press doubletap');
        }

        public setViewPortSize(size: Dimension): void {
            this.viewer.style.width = size.width + 'px';
            this.viewer.style.height = size.height + 'px';

            this.adjustZoom();
        }

        public getViewPortSize(): Dimension {
            return {
                width: this.viewer.clientWidth,
                height: this.viewer.clientHeight
            };
        }

        public getDocumentSize(): Dimension {
            return {
                width: this.viewerContent.clientWidth,
                height: this.viewerContent.clientHeight
            };
        }

        public setSource(source: string): void {
            this.viewerContent.src = source;
            this.viewerContent.onload = () => {
                console.info("image loaded");
                this.adjustZoom();
            };
        }

        private adjustZoom(): void {

            var size: Dimension = this.getViewPortSize();
            var documentSize: Dimension = this.getDocumentSize();
            console.info("adjust zoom, documentSize: " + documentSize.width + "x" + documentSize.height);
            console.info("adjust zoom, viewPortSize: " + size.width + "x" + size.height);

            this.minScale = 100 / documentSize.width;

            console.info("minScale=" + this.minScale);

            var widthScale: number = size.width / documentSize.width;
            var heightScale: number = size.height / documentSize.height;
            var scale: number = Math.min(widthScale, heightScale);

            var left: number = (size.width - documentSize.width) / 2;
            var top: number = (size.height - documentSize.height) / 2;

            console.log("setting content to : left => " + left + "  , top => " + top, ", scale => ", scale);

            this.viewerContent.style.left = left + 'px';
            this.viewerContent.style.top = top + 'px';

            this.transform.scale = scale;
            this.updateElementTransform();
        }

        private onPinch(ev: HammerInput): void {

            var pinchCenter: Point = new Point(ev.center.x - this.viewer.offsetLeft, ev.center.y - this.viewer.offsetTop);

            console.info("pinch - center=" + pinchCenter + " scale=" + ev.scale);
            if (ev.type == 'pinchstart') {
                this.pinchInitialScale = this.transform.scale || 1;
            }

            var targetScale: number = this.pinchInitialScale * ev.scale;
            if (targetScale <= this.minScale) {
                targetScale = this.minScale;
            }

            if (Math.abs(this.transform.scale - this.minScale) < 1e-10
                && Math.abs(targetScale - this.minScale) < 1e-10) {
                console.debug('already at min scale');
                this.requestElementUpdate();
                return;
            }

            this.zoomTo(new Point(ev.center.x, ev.center.y), targetScale);
        }

        private onImageClick(event: MouseEvent) {
            console.info("click");

            var zoomCenter = new Point(event.pageX - this.viewer.offsetLeft, event.pageY - this.viewer.offsetTop);
            var scaleFactor = event.shiftKey || event.ctrlKey ? 0.75 : 1.25;
            this.zoomTo(zoomCenter, scaleFactor * this.transform.scale);
        }

        private zoomTo(zoomCenter: Point, newScale: number): void {
            var viewPortSize: Dimension = this.getViewPortSize();
            var viewPortCenter: Point = new Point(viewPortSize.width / 2, viewPortSize.height / 2);

            var zoomRelativeCenter: Point = new Point(zoomCenter.x - viewPortCenter.x, zoomCenter.y - viewPortCenter.y);
            console.debug('clicked at ' + zoomRelativeCenter + ' (relative to center)');

            var oldScale: number = this.transform.scale;

            // calculate translate difference 

            // 1. center on new coordinates
            var zoomDx: number = -(zoomRelativeCenter.x) / oldScale;
            var zoomDy: number = -(zoomRelativeCenter.y) / oldScale;

            // 2. translate from center to clicked point with new zoom
            zoomDx += (zoomRelativeCenter.x) / newScale;
            zoomDy += (zoomRelativeCenter.y) / newScale;

            console.debug('dx=' + zoomDx + ' dy=' + zoomDy + ' oldScale=' + oldScale);

            /// move to the difference
            this.transform.translate.x += zoomDx;
            this.transform.translate.y += zoomDy;

            this.transform.scale = newScale;
            console.debug("applied zoom: scale= " + this.transform.scale + ' translate=' + this.transform.translate);
            this.requestElementUpdate();
        }

        private requestElementUpdate() {
            if (!this.ticking) {
                window.requestAnimationFrame(() => { this.updateElementTransform() });
                this.ticking = true;
            }
        }

        private updateElementTransform() {

            var value = [
                'rotate(' + this.transform.angle + 'deg)',
                'scale(' + this.transform.scale + ', ' + this.transform.scale + ')',
                'translate3d(' + this.transform.translate.x + 'px, ' + this.transform.translate.y + 'px, 0px)',
            ];

            var stringValue: string = value.join(" ");
            console.debug("transform = " + stringValue);
            this.viewerContent.style.transform = stringValue;
            (<any>this.viewerContent.style).webkitTransform = stringValue;
            (<any>this.viewerContent.style).MozTransform = stringValue;
            (<any>this.viewerContent.style).msTransform = stringValue;
            (<any>this.viewerContent.style).OTransform = stringValue;

            this.ticking = false;
        }
    }
}
person Louis GRIGNON    schedule 15.06.2016