【问题标题】:Zoom on tap/pinch on a div element using css transform translate and scale with hammerjs使用 css transform 缩放 div 元素上的点击/捏合,使用hammerjs进行平移和缩放
【发布时间】:2014-10-04 12:50:20
【问题描述】:

我有一个容器,可以包含其他 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>

当我单击(使用鼠标)或点击/捏住#mycontainer div 的一部分时,我想缩放#mycontainer div 及其内容(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 Zoom in on a point (using scale and translate)

我不想使用 transform-origin 我只想使用 transform:translate() 和 transform:scale()

我想在http://hammerjs.github.io/ 主页演示中喜欢这个实现。 尝试捏住白色框,它会缩放和旋转。我不需要旋转代码,只需缩放

请帮忙!

【问题讨论】:

    标签: zooming hammer.js pinch


    【解决方案1】:

    查看/缩放和平移图像是个棘手的问题,对吧? :)

    我终于成功校准了缩放算法,所以我想与社区分享。我创建了一个查看器类来与底层图像交互。我的解决方案中的一个重点是它不会修改默认的转换原点,这可能对其他一些转换有用。

    您可以使用 click 来缩放 / ctrl + click 来取消缩放,或捏合捏出(使用 Hammer JS)。警告,Firefox 默认不启用触摸事件。

    对不起,我知道它使用 Hammer 和自制的 Transform & Point 类,但请关注 zoomTo 方法,它与框架无关,是这个缩放问题的重点。

    (如果您愿意,可以在下面找到 TypeScript 版本)

    在这个 sn-p 中尝试一下

    // 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;
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-09-27
      • 2013-12-12
      • 1970-01-01
      • 2023-03-27
      • 2013-01-17
      • 2017-03-24
      • 1970-01-01
      • 2016-11-25
      相关资源
      最近更新 更多