माउस कर्सर पर CSS3 ज़ूमिंग




javascript-events zoom (2)

मेरा लक्ष्य एक ऐसा प्लग-इन बनाना है जो एक पृष्ठ क्षेत्र पर ज़ूमिंग और पैनिंग संचालन को सक्षम करता है, जैसे कि Google मानचित्र वर्तमान में काम करता है (जिसका अर्थ है: माउस के साथ स्क्रॉल करना = क्षेत्र में / बाहर ज़ूमिंग करने से, क्लिक करें और दबाए रखें और छोड़ें और रिलीज़ करें = पॅनिंग )।

स्क्रॉल करते समय, मैं माउस कर्सर पर केंद्रित एक ज़ूम कार्रवाई करना चाहता हूं।

इसके लिए, मैं मक्खी- CSS3 मैट्रिक्स परिवर्तनों का उपयोग करता हूं। केवल, फिर भी अनिवार्य, बाधा यह है कि मैं CSS3 अनुवाद और पैमाने परिवर्तनों के अलावा कुछ भी नहीं उपयोग कर सकता है, 0px 0px की उत्पत्ति परिमाण के साथ।

पनैनिंग मेरे प्रश्न के दायरे से बाहर है, क्योंकि मुझे यह पहले ही काम कर रहा है जब ज़ूमिंग की बात आती है, तो मैं यह पता लगाने में संघर्ष कर रहा हूं कि मेरे जावास्क्रिप्ट कोड में गड़बड़ कहाँ है I

एक्सज़ और वाई अक्ष पर अनुवाद की गणना में समस्या कहीं माउसज़ूम.प्रोटोटाइप ज़ूम फ़ंक्शन में होनी चाहिए।

सबसे पहले, मेरा HTML कोड है:

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width = device-width, initial-scale = 1.0, user-scalable = no" />
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black" />
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
    <script src="jquery.mousewheel.min.js"></script>
    <script src="StackOverflow.js"></script>
    <style type="text/css" media="all">
        #drawing {
            position: absolute;
            top: 0px; 
            left: 0px; 
            right:0; 
            bottom:0;
            z-index: 0;
            background: url(http://catmacros.files.wordpress.com/2009/09/cats_banzai.jpg) no-repeat;
            background-position: 50% 50%;
        }
    </style>
    <title>Test</title>
</head>
<body>
    <div id="drawing"></div>
    <script>
        var renderer = new ZoomPanRenderer("drawing");
    </script>
</body>
</html>

जैसा कि आप देख सकते हैं, मैं ब्रांडेन हारून से जेक्जरी और जेकोजी माउस व्हील प्लगइन का उपयोग कर रहा हूं, जो यहां पाई जा सकती है: https://github.com/brandonaaron/jquery-mousewheel/

यहाँ StackOverflow.js फ़ाइल की सामग्री है:

/***************************************************** 
 * Transformations
 ****************************************************/
function Transformations(translateX, translateY, scale){
    this.translateX = translateX;
    this.translateY = translateY;
    this.scale = scale;
}

/* Getters */
Transformations.prototype.getScale = function(){ return this.scale; }
Transformations.prototype.getTranslateX = function(){ return this.translateX; }
Transformations.prototype.getTranslateY = function(){ return this.translateY; }

/***************************************************** 
 * Zoom Pan Renderer
 ****************************************************/
function ZoomPanRenderer(elementId){
    this.zooming = undefined;
    this.elementId = elementId;
    this.current = new Transformations(0, 0, 1);
    this.last = new Transformations(0, 0, 1);
    new ZoomPanEventHandlers(this);
}

/* setters */
ZoomPanRenderer.prototype.setCurrentTransformations = function(t){ this.current = t; }
ZoomPanRenderer.prototype.setZooming = function(z){ this.zooming = z; }

/* getters */
ZoomPanRenderer.prototype.getCurrentTransformations = function(){ return this.current; }
ZoomPanRenderer.prototype.getZooming = function(){ return this.zooming; }
ZoomPanRenderer.prototype.getLastTransformations = function(){ return this.last; }
ZoomPanRenderer.prototype.getElementId = function(){ return this.elementId; }

/* Rendering */
ZoomPanRenderer.prototype.getTransform3d = function(t){
    var transform3d = "matrix3d(";
    transform3d+= t.getScale().toFixed(10) + ",0,0,0,";
    transform3d+= "0," + t.getScale().toFixed(10) + ",0,0,";
    transform3d+= "0,0,1,0,";
    transform3d+= t.getTranslateX().toFixed(10) + "," + t.getTranslateY().toFixed(10)  + ",0,1)";
    return transform3d;
}

ZoomPanRenderer.prototype.getTransform2d = function(t){
    var transform3d = "matrix(";
    transform3d+= t.getScale().toFixed(10) + ",0,0," + t.getScale().toFixed(10) + "," + t.getTranslateX().toFixed(10) + "," + t.getTranslateY().toFixed(10) + ")";
    return transform3d;
}

ZoomPanRenderer.prototype.applyTransformations = function(t){
    var elem = $("#" + this.getElementId());
    elem.css("transform-origin", "0px 0px");
    elem.css("-ms-transform-origin", "0px 0px");
    elem.css("-o-transform-origin", "0px 0px");
    elem.css("-moz-transform-origin", "0px 0px");
    elem.css("-webkit-transform-origin", "0px 0px");
    var transform2d = this.getTransform2d(t);
    elem.css("transform", transform2d);
    elem.css("-ms-transform", transform2d);
    elem.css("-o-transform", transform2d);
    elem.css("-moz-transform", transform2d);
    elem.css("-webkit-transform", this.getTransform3d(t));
}

/***************************************************** 
 * Event handler
 ****************************************************/
function ZoomPanEventHandlers(renderer){
    this.renderer = renderer;

    /* Disable scroll overflow - safari */
    document.addEventListener('touchmove', function(e) { e.preventDefault(); }, false);

    /* Disable default drag opeartions on the element (FF makes it ready for save)*/
    $("#" + renderer.getElementId()).bind('dragstart', function(e) { e.preventDefault(); });

    /* Add mouse wheel handler */
    $("#" + renderer.getElementId()).bind("mousewheel", function(event, delta) {
        if(renderer.getZooming()==undefined){
            var offsetLeft = $("#" + renderer.getElementId()).offset().left;
            var offsetTop = $("#" + renderer.getElementId()).offset().top;
            var zooming = new MouseZoom(renderer.getCurrentTransformations(), event.pageX, event.pageY, offsetLeft, offsetTop, delta);
            renderer.setZooming(zooming);

            var newTransformation = zooming.zoom();
            renderer.applyTransformations(newTransformation);
            renderer.setCurrentTransformations(newTransformation);
            renderer.setZooming(undefined);
        }
        return false;
    });
}

/***************************************************** 
 * Mouse zoom
 ****************************************************/
function MouseZoom(t, mouseX, mouseY, offsetLeft, offsetTop, delta){
    this.current = t;
    this.offsetLeft = offsetLeft;
    this.offsetTop = offsetTop;
    this.mouseX = mouseX;
    this.mouseY = mouseY;
    this.delta = delta;
}

MouseZoom.prototype.zoom = function(){
    var previousScale = this.current.getScale();
    var newScale = previousScale + this.delta/5;
    if(newScale<1){
        newScale = 1;
    }
    var ratio = newScale / previousScale;

    var imageX = this.mouseX - this.offsetLeft;
    var imageY = this.mouseY - this.offsetTop;

    var previousTx = - this.current.getTranslateX() * previousScale;
    var previousTy = - this.current.getTranslateY() * previousScale;
    var previousDx = imageX * previousScale;
    var previousDy = imageY * previousScale;

    var newTx = (previousTx * ratio + previousDx * (ratio - 1)) / newScale;
    var newTy = (previousTy * ratio + previousDy * (ratio - 1)) / newScale;

    return new Transformations(-newTx, -newTy, newScale);
}

एक div तत्व पर व्यवहार zooming एक गूगल मैप्स पाने के लिए transform का उपयोग एक दिलचस्प विचार की तरह लग रहा था, इसलिए मैं इसके साथ थोड़ी देर payed =)

मैं डिज़ाइन पर माउस स्थान पर ज़ूमिंग को समायोजित करने के लिए transform-origin (और ब्राउज़र संगतता के लिए इसकी बहन विशेषताएँ) का उपयोग करूँगा कि आप स्केलिंग हैं। मुझे लगता है कि यह ऐसा कर सकता है जो आप चाहते हैं उदाहरण के लिए मैंने बेला पर कुछ उदाहरण दिए:

transform-origin समायोजित करना

तो आपके में लागू applyTransformations समारोह में हम transform-origin गतिशील रूप से imageX और imageY से समायोजित कर सकते हैं, अगर हम MouseZoom (माउस श्रोता) फ़ंक्शन से इन मानों को पास करते हैं।

    var orig = t.getTranslateX().toFixed() + "px " + t.getTranslateY().toFixed() + "px";
    elem.css("transform-origin", orig);
    elem.css("-ms-transform-origin", orig);
    elem.css("-o-transform-origin", orig);
    elem.css("-moz-transform-origin", orig);
    elem.css("-webkit-transform-origin", orig);

(इस पहले बेल्ड उदाहरण में मैं सिर्फ अपने translateX में originX और originY originX तत्व पर माउस को स्थानांतरित करने के लिए - दूसरे उदाहरण में मैंने इसे अनुवाद originY से अलग करने के लिए इसे originX और originY नाम दिया।)

परिवर्तन मूल की गणना

आपके MouseZoom हम मूल स्थान की गणना कर सकते हैं बस imageX/previousScale साथ

    MouseZoom.prototype.zoom = function(){
        var previousScale = this.current.getScale();
        var newScale = previousScale + this.delta/10;
        if(newScale<1){
            newScale = 1;
        }
        var ratio = newScale / previousScale;

        var imageX = this.mouseX - this.offsetLeft;
        var imageY = this.mouseY - this.offsetTop;

        var newTx = imageX/previousScale;
        var newTy = imageY/previousScale;

        return new Transformations(newTx, newTy, newScale);
    }

तो यह पूरी तरह से काम करेगा यदि आप अलग स्थिति पर ज़ूमिंग करने से पहले पूरी तरह से ज़ूम आउट करते हैं। लेकिन किसी भी ज़ूम स्तर पर ज़ूम मूल को बदलने में सक्षम होने के लिए, हम मूल और अनुवाद की कार्यक्षमता को जोड़ सकते हैं।

ज़ूमिंग फ़्रेम का स्थानांतरण करना (मेरे मूल उत्तर का विस्तार करना)

छवि पर रूपांतरण की उत्पत्ति अभी भी उसी तरह की गणना की जाती है, लेकिन ज़ूमिंग फ़्रेम को बदलने के लिए हम अलग translateX और translateY का उपयोग करते हैं (यहां मैंने दो नई वैरिएबल की शुरुआत की थी जो हमारी चाल में मदद करती है - तो अब हमारे पास originX , originY , translateX और translateY originY )।

    MouseZoom.prototype.zoom = function(){
        // current scale
        var previousScale = this.current.getScale();
        // new scale
        var newScale = previousScale + this.delta/10;
        // scale limits
        var maxscale = 20;
        if(newScale<1){
            newScale = 1;
        }
        else if(newScale>maxscale){
            newScale = maxscale;
        }
        // current cursor position on image
        var imageX = (this.mouseX - this.offsetLeft).toFixed(2);
        var imageY = (this.mouseY - this.offsetTop).toFixed(2);
        // previous cursor position on image
        var prevOrigX = (this.current.getOriginX()*previousScale).toFixed(2);
        var prevOrigY = (this.current.getOriginY()*previousScale).toFixed(2);
        // previous zooming frame translate
        var translateX = this.current.getTranslateX();
        var translateY = this.current.getTranslateY();
        // set origin to current cursor position
        var newOrigX = imageX/previousScale;
        var newOrigY = imageY/previousScale;
        // move zooming frame to current cursor position
        if ((Math.abs(imageX-prevOrigX)>1 || Math.abs(imageY-prevOrigY)>1) && previousScale < maxscale) {
            translateX = translateX + (imageX-prevOrigX)*(1-1/previousScale);
            translateY = translateY + (imageY-prevOrigY)*(1-1/previousScale);
        }
        // stabilize position by zooming on previous cursor position
        else if(previousScale != 1 || imageX != prevOrigX && imageY != prevOrigY) {
            newOrigX = prevOrigX/previousScale;
            newOrigY = prevOrigY/previousScale;
        }
        return new Transformations(newOrigX, newOrigY, translateX, translateY, newScale);
    }

इस उदाहरण के लिए मैंने अपनी मूल स्क्रिप्ट को थोड़ा और समायोजित किया और दूसरा बेल्ड उदाहरण जोड़ा।

अब हम किसी भी ज़ूम स्तर से माउस कर्सर पर ज़ूम इन और आउट करते हैं। लेकिन फ्रेम बदलाव की वजह से हम मूल डिवी के चारों ओर ("पृथ्वी को मापने") चलते हैं ... जो मज़ेदार दिखता है यदि आप सीमित चौड़ाई के एक ऑब्जेक्ट के साथ काम करते हैं (ज़ूम इन में एक छोर पर, ज़ूम-आउट एक और अंत, और हम एक inchworm की तरह आगे चले गए)

"इंचवॉर्म" प्रभाव से बचना

इस से बचने के लिए आप उदाहरण के लिए सीमाएं जोड़ सकते हैं ताकि बाएं छवि सीमा अपने मूल एक्स समन्वय के दायरे में नहीं जा सकें, शीर्ष छवि सीमा इसकी मूल y स्थिति से कम नहीं जा सकती है, और इसी तरह अन्य दो सीमाओं के लिए। लेकिन फिर ज़ूम / आउट पूरी तरह से कर्सर के लिए बाध्य नहीं होंगे, बल्कि छवि के किनारे से (आप चित्र स्लाइड को जगह में देखेंगे) उदाहरण 3 में

    if(this.delta <= 0){
        var width = 500; // image width
        var height = 350; // image height
        if(translateX+newOrigX+(width - newOrigX)*newScale <= width){
            translateX = 0;
            newOrigX = width;
        }
        else if (translateX+newOrigX*(1-newScale) >= 0){
            translateX = 0;
            newOrigX = 0;        
        }
        if(translateY+newOrigY+(height - newOrigY)*newScale <= height){
            translateY = 0;
            newOrigY = height;
        }
        else if (translateY+newOrigY*(1-newScale) >= 0){
            translateY = 0;
            newOrigY = 0;
        }
    }

एक और (एक बिट गड़बड़ी) विकल्प केवल फ्रेम रीसेट करने के लिए होगा जब आप पूरी तरह ज़ूम आउट कर लें (पैमाने == 1)।

हालांकि, यदि आप निरंतर तत्वों (बाएं और दाएं किनारे और ऊपर और नीचे किनारे एक साथ बंधे हुए) या केवल बहुत बड़े तत्वों के साथ काम कर रहे हैं, तो आपको यह समस्या नहीं होगी।

एक अच्छा स्पर्श के साथ सब कुछ खत्म करने के लिए - हम अपने स्केलिंग ऑब्जेक्ट के आसपास छिपी हुई अतिप्रवाह के साथ एक मूल फ्रेम जोड़ सकते हैं। इसलिए छवि क्षेत्र झूमिंग के साथ नहीं बदलता है। Jsfiddle उदाहरण 4 देखें


इसके लिए हमने एक प्रतिक्रिया पुस्तकालय बनाया: https://www.npmjs.com/package/react-map-interaction

यह मोबाइल और डेस्कटॉप दोनों पर ज़ूमिंग और पॅनिंग और काम करता है।

स्रोत काफी छोटा और पठनीय है, लेकिन यहां और अधिक सीधे आपके प्रश्न का उत्तर देने के लिए, हम इस सीएसएस रूपांतरण का उपयोग करते हैं:

const transform = `translate(${translation.x}px, ${translation.y}px) scale(${scale})`;
const style = {
    transform: transform,
    transformOrigin: '0 0 '
};

// render the div with that style

प्रारंभिक पॉइंटर / माउस डाउन राज्य और वर्तमान स्थिति के बीच जब एक टच / माउस चाल होता है, तो प्राथमिक चालियों में से एक ठीक से अंतर की गणना कर रहा है। जब माउस नीचे होता है, तो निर्देशांक कैप्चर करें। फिर हर माउस चाल पर (एक माउस ऊपर तक) अंतर में अंतर की गणना करें यह अंतर यह है कि आप अपने कर्सर के अंतर्गत शुरुआती बिंदु ज़ूम के फोकल बिंदु है, यह सुनिश्चित करने के लिए अनुवाद को ऑफसेट करने की आवश्यकता है।





coordinate-transformation