【问题标题】:jQuery positioning with CSS3 "transform: scale"使用 CSS3“变换:缩放”的 jQuery 定位
【发布时间】:2016-10-22 17:23:43
【问题描述】:

jQuery 不适用于 CSS "transform: scale()" (但是使用“transform: translate()”可以正常工作)

请看一下这个简单的例子:

$(document).ready(function() {

  $('#root').dblclick(function() {
    $('#box').position({
      my: 'right bottom',
      at: 'right bottom',
      of: $('#root')
    });
  })

  $('#box').draggable({
    containment: $('#root'),
  });

});
body {
  position: relative;
  margin: 0;
}
#root {
  position: absolute;
  top: 20px;
  left: 20px;
  width: 500px;
  height: 500px;
  border: solid 2px red;
  transform-origin: 0 0 0;
  transform: scale(0.5);
}
#box {
  position: absolute;
  top: 100px;
  left: 50px;
  display: inline-block;
  width: 50px;
  height: 50px;
  background: red;
  border: solid 1px black;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>

drag red box :)
<br/>double click in square to position box
<div id="root">
  <div id="box"></div>
</div>

根节点必须缩放,因为在我的真实应用程序中我使用全屏模式并且我需要使内容适合窗口分辨率。 但是当我缩放父元素时,jQuery UI 可拖动和 jQuery 位置不能正常工作。

当然问题是如何让它正常工作?

有很多类似的问题,但我没有找到正确的答案。

【问题讨论】:

    标签: javascript jquery css css-transforms


    【解决方案1】:

    我通过 Martii Laine 改编了 this answer 以说明包含和双击定位:

    $(document).ready(function () {
    
        var $root = $('#root');
        var $box = $('#box');
      
        var minLeft = parseFloat($root.css("paddingLeft"));
        var minTop = parseFloat($root.css("paddingTop"));
        var maxLeft = minLeft + $root.width() - $box.outerWidth();
        var maxTop = minTop + $root.height() - $box.outerHeight();
    
        $root.dblclick(function () {
            $box.css({
                left: maxLeft,
                top: maxTop
            });
        })
        
        var zoom = 0.5;
        var click = { x: 0, y: 0 };
      
        $box.draggable({
            start: function (event) {
                click.x = event.clientX;
                click.y = event.clientY;
            },
          
            drag: function (event, ui) {
                var original = ui.originalPosition;
                var left = (event.clientX - click.x + original.left) / zoom;
                var top = (event.clientY - click.y + original.top) / zoom; 
                ui.position = {
                    left: Math.max(minLeft, Math.min(maxLeft, left)),
                    top: Math.max(minTop, Math.min(maxTop, top))
                };
            }
        });
    });
    body
    {
        position: relative;
        margin: 0;
    }
    
    #root
    {
        position: absolute;
        top: 20px;
        left: 20px;
        width: 500px;
        height: 500px;
        border: solid 2px red;
        transform-origin: 0 0 0;
        transform: scale(0.5);
    }
    
    #box
    {
        position: absolute;
        top: 100px;
        left: 50px;
        display: inline-block;
        width: 50px;
        height: 50px;
        background: red;
        border: solid 1px black;
    }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
    
    drag red box :)
    <br/>double click in square to position box
    <div id="root">
        <div id="box"></div>
    </div>

    如果在根div中双击时需要其他对齐方式,代码:

    $root.dblclick(function () {
        $box.css({
            left: maxLeft,
            top: maxTop
        });
    })
    

    可以修改如下:

    left: minLeft,                  // Left aligned
    left: maxLeft,                  // Right aligned
    left: (minLeft + maxLeft) / 2,  // Centered (horizontally)
    
    top: minTop,                    // At the top
    top: maxTop,                    // At the Bottom
    top: (minTop + maxTop) / 2,     // Centered (vertically)
    

    【讨论】:

    • +0.5 表示可拖动 - 但它仍然不完美。您的位置解决方案不可接受,因为设置可能会有所不同。以任何方式谢谢
    • 可能是左、中、右、上、下。我还需要碰撞检测、翻转等功能。
    • 我在答案中添加了 lefttop 值,用于各种可能的对齐方式。
    【解决方案2】:

    我将我的内容放入 iframe,并在 iframe 上应用了转换 - 它工作正常 :)

    编辑:我的解决方案的以前版本结果是错误的。但是如果有人想检查它,我会保存 sn-p

    /*
     * jQuery UI FIX
     * Take focus on window.transformScale
     */
    
    /*
     * Offset fix
     */
    (function() {
    
    function getWindow(elem) { return jQuery.isWindow(elem) ? elem : elem.nodeType === 9 && elem.defaultView; }
    
    jQuery.fn.offset = function( options ) {
    
    	// Preserve chaining for setter
    	if ( arguments.length ) {
    		return options === undefined ?
    			this :
    			this.each( function( i ) {
    				jQuery.offset.setOffset( this, options, i );
    			} );
    	}
    
    	var docElem, win, rect, doc,
    		elem = this[ 0 ];
    
    	if ( !elem ) {
    		return;
    	}
    
    	// Support: IE <=11 only
    	// Running getBoundingClientRect on a
    	// disconnected node in IE throws an error
    	if ( !elem.getClientRects().length ) {
    		return { top: 0, left: 0 };
    	}
    
    	var transform = $(document.body).css('transform');
    	$(document.body).css('transform', 'none');
    
    	rect = elem.getBoundingClientRect();
    
    	$(document.body).css('transform', transform);
    
    	// Make sure element is not hidden (display: none)
    	if ( rect.width || rect.height ) {
    		doc = elem.ownerDocument;
    		win = getWindow( doc );
    		docElem = doc.documentElement;
    
    		return {
    			top: rect.top + (win.pageYOffset - docElem.clientTop) / window.transformScale,
    			left: rect.left + (win.pageXOffset - docElem.clientLeft) / window.transformScale,
    		};
    	}
    
    	// Return zeros for disconnected and hidden elements (gh-2310)
    	return rect;
    };
    
    })();
    
    
    /*
     * Position fix
     */
    (function() {
    
    var cachedScrollbarWidth,
    	max = Math.max,
    	abs = Math.abs,
    	rhorizontal = /left|center|right/,
    	rvertical = /top|center|bottom/,
    	roffset = /[\+\-]\d+(\.[\d]+)?%?/,
    	rposition = /^\w+/,
    	rpercent = /%$/,
    	_position = $.fn.position;
    
    function getOffsets( offsets, width, height ) {
    	return [
    		parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
    		parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
    	];
    }
    
    function parseCss( element, property ) {
    	return parseInt( $.css( element, property ), 10 ) || 0;
    }
    
    function getDimensions( elem ) {
    	var raw = elem[ 0 ];
    	if ( raw.nodeType === 9 ) {
    		return {
    			width: elem.width() / window.transformScale,
    			height: elem.height() / window.transformScale,
    			offset: { top: 0, left: 0 }
    		};
    	}
    	if ( $.isWindow( raw ) ) {
    		return {
    			width: elem.width() / window.transformScale,
    			height: elem.height() / window.transformScale,
    			offset: { top: elem.scrollTop(), left: elem.scrollLeft() }
    		};
    	}
    	if ( raw.preventDefault ) {
    		return {
    			width: 0,
    			height: 0,
    			offset: { top: raw.pageY, left: raw.pageX }
    		};
    	}
    	return {
    		width: elem.outerWidth() / window.transformScale,
    		height: elem.outerHeight() / window.transformScale,
    		offset: elem.offset()
    	};
    }
    
    jQuery.fn.position = function( options ) {
    	if ( !options || !options.of ) {
    		return _position.apply( this, arguments );
    	}
    
    	// Make a copy, we don't want to modify arguments
    	options = $.extend( {}, options );
    
    	var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions,
    		target = $( options.of ),
    		within = $.position.getWithinInfo( options.within ),
    		scrollInfo = $.position.getScrollInfo( within ),
    		collision = ( options.collision || "flip" ).split( " " ),
    		offsets = {};
    
    	dimensions = getDimensions( target );
    	if ( target[ 0 ].preventDefault ) {
    
    		// Force left top to allow flipping
    		options.at = "left top";
    	}
    	targetWidth = dimensions.width;
    	targetHeight = dimensions.height;
    	targetOffset = dimensions.offset;
    
    	// Clone to reuse original targetOffset later
    	basePosition = $.extend( {}, targetOffset );
    
    	// Force my and at to have valid horizontal and vertical positions
    	// if a value is missing or invalid, it will be converted to center
    	$.each( [ "my", "at" ], function() {
    		var pos = ( options[ this ] || "" ).split( " " ),
    			horizontalOffset,
    			verticalOffset;
    
    		if ( pos.length === 1 ) {
    			pos = rhorizontal.test( pos[ 0 ] ) ?
    				pos.concat( [ "center" ] ) :
    				rvertical.test( pos[ 0 ] ) ?
    					[ "center" ].concat( pos ) :
    					[ "center", "center" ];
    		}
    		pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
    		pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";
    
    		// Calculate offsets
    		horizontalOffset = roffset.exec( pos[ 0 ] );
    		verticalOffset = roffset.exec( pos[ 1 ] );
    		offsets[ this ] = [
    			horizontalOffset ? horizontalOffset[ 0 ] : 0,
    			verticalOffset ? verticalOffset[ 0 ] : 0
    		];
    
    		// Reduce to just the positions without the offsets
    		options[ this ] = [
    			rposition.exec( pos[ 0 ] )[ 0 ],
    			rposition.exec( pos[ 1 ] )[ 0 ]
    		];
    	} );
    
    	// Normalize collision option
    	if ( collision.length === 1 ) {
    		collision[ 1 ] = collision[ 0 ];
    	}
    
    	if ( options.at[ 0 ] === "right" ) {
    		basePosition.left += targetWidth;
    	} else if ( options.at[ 0 ] === "center" ) {
    		basePosition.left += targetWidth / 2;
    	}
    
    	if ( options.at[ 1 ] === "bottom" ) {
    		basePosition.top += targetHeight;
    	} else if ( options.at[ 1 ] === "center" ) {
    		basePosition.top += targetHeight / 2;
    	}
    
    	atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
    	basePosition.left += atOffset[ 0 ];
    	basePosition.top += atOffset[ 1 ];
    
    	return this.each( function() {
    		var collisionPosition, using,
    			elem = $( this ),
    			elemWidth = elem.outerWidth() / window.transformScale,
    			elemHeight = elem.outerHeight() / window.transformScale,
    			marginLeft = parseCss( this, "marginLeft" ),
    			marginTop = parseCss( this, "marginTop" ),
    			collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) +
    				scrollInfo.width,
    			collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) +
    				scrollInfo.height,
    			position = $.extend( {}, basePosition ),
    			myOffset = getOffsets( offsets.my, elem.outerWidth() / window.transformScale, elem.outerHeight() / window.transformScale );
    
    		if ( options.my[ 0 ] === "right" ) {
    			position.left -= elemWidth;
    		} else if ( options.my[ 0 ] === "center" ) {
    			position.left -= elemWidth / 2;
    		}
    
    		if ( options.my[ 1 ] === "bottom" ) {
    			position.top -= elemHeight;
    		} else if ( options.my[ 1 ] === "center" ) {
    			position.top -= elemHeight / 2;
    		}
    
    		position.left += myOffset[ 0 ];
    		position.top += myOffset[ 1 ];
    
    		collisionPosition = {
    			marginLeft: marginLeft,
    			marginTop: marginTop
    		};
    
    		$.each( [ "left", "top" ], function( i, dir ) {
    			if ( jQuery.ui.position[ collision[ i ] ] ) {
    				jQuery.ui.position[ collision[ i ] ][ dir ]( position, {
    					targetWidth: targetWidth,
    					targetHeight: targetHeight,
    					elemWidth: elemWidth,
    					elemHeight: elemHeight,
    					collisionPosition: collisionPosition,
    					collisionWidth: collisionWidth,
    					collisionHeight: collisionHeight,
    					offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
    					my: options.my,
    					at: options.at,
    					within: within,
    					elem: elem
    				} );
    			}
    		} );
    
    		if ( options.using ) {
    
    			// Adds feedback as second argument to using callback, if present
    			using = function( props ) {
    				var left = targetOffset.left - position.left,
    					right = left + targetWidth - elemWidth,
    					top = targetOffset.top - position.top,
    					bottom = top + targetHeight - elemHeight,
    					feedback = {
    						target: {
    							element: target,
    							left: targetOffset.left,
    							top: targetOffset.top,
    							width: targetWidth,
    							height: targetHeight
    						},
    						element: {
    							element: elem,
    							left: position.left,
    							top: position.top,
    							width: elemWidth,
    							height: elemHeight
    						},
    						horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
    						vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
    					};
    				if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
    					feedback.horizontal = "center";
    				}
    				if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
    					feedback.vertical = "middle";
    				}
    				if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
    					feedback.important = "horizontal";
    				} else {
    					feedback.important = "vertical";
    				}
    				options.using.call( this, props, feedback );
    			};
    		}
    
    		elem.offset( $.extend( position, { using: using } ) );
    	} );
    };
    
    })();
    
    
    /*
     * Draggable fix
     */
    (function() {
    
    jQuery.ui.draggable.prototype._refreshOffsets = function( event ) {
    	this.offset = {
    		top: this.positionAbs.top - this.margins.top,
    		left: this.positionAbs.left - this.margins.left,
    		scroll: false,
    		parent: this._getParentOffset(),
    		relative: this._getRelativeOffset()
    	};
    
    	this.offset.click = {
    		left: event.pageX / window.transformScale - this.offset.left,
    		top: event.pageY / window.transformScale - this.offset.top
    	};
    };
    
    jQuery.ui.draggable.prototype._generatePosition = function( event, constrainPosition ) {
    
    	var containment, co, top, left,
    		o = this.options,
    		scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] ),
    		pageX = event.pageX / window.transformScale,
    		pageY = event.pageY / window.transformScale;
    
    	// Cache the scroll
    	if ( !scrollIsRootNode || !this.offset.scroll ) {
    		this.offset.scroll = {
    			top: this.scrollParent.scrollTop(),
    			left: this.scrollParent.scrollLeft()
    		};
    	}
    
    	/*
    	 * - Position constraining -
    	 * Constrain the position to a mix of grid, containment.
    	 */
    
    	// If we are not dragging yet, we won't check for options
    	if ( constrainPosition ) {
    		if ( this.containment ) {
    			if ( this.relativeContainer ) {
    				co = this.relativeContainer.offset();
    				containment = [
    					this.containment[ 0 ] + co.left,
    					this.containment[ 1 ] + co.top,
    					this.containment[ 2 ] + co.left,
    					this.containment[ 3 ] + co.top
    				];
    			} else {
    				containment = this.containment;
    			}
    
    			var width = 0;
    			var height = 0;
    			if(window.transformScale != 1)
    			{
    				var width = this.helper.outerWidth();
    				var height = this.helper.outerHeight();
    			}
    
    			if ( pageX - this.offset.click.left < containment[ 0 ] ) {
    				pageX = containment[ 0 ] + this.offset.click.left;
    			}
    			if ( pageY - this.offset.click.top < containment[ 1 ] ) {
    				pageY = containment[ 1 ] + this.offset.click.top;
    			}
    			if ( pageX - this.offset.click.left + width > containment[ 2 ] ) {
    				pageX = containment[ 2 ] + this.offset.click.left - width;
    			}
    			if ( pageY - this.offset.click.top + height > containment[ 3 ] ) {
    				pageY = containment[ 3 ] + this.offset.click.top - height;
    			}
    		}
    
    		if ( o.grid ) {
    
    			//Check for grid elements set to 0 to prevent divide by 0 error causing invalid
    			// argument errors in IE (see ticket #6950)
    			top = o.grid[ 1 ] ? this.originalPageY + Math.round( ( pageY -
    				this.originalPageY ) / o.grid[ 1 ] ) * o.grid[ 1 ] : this.originalPageY;
    			pageY = containment ? ( ( top - this.offset.click.top >= containment[ 1 ] ||
    				top - this.offset.click.top > containment[ 3 ] ) ?
    					top :
    					( ( top - this.offset.click.top >= containment[ 1 ] ) ?
    						top - o.grid[ 1 ] : top + o.grid[ 1 ] ) ) : top;
    
    			left = o.grid[ 0 ] ? this.originalPageX +
    				Math.round( ( pageX - this.originalPageX ) / o.grid[ 0 ] ) * o.grid[ 0 ] :
    				this.originalPageX;
    			pageX = containment ? ( ( left - this.offset.click.left >= containment[ 0 ] ||
    				left - this.offset.click.left > containment[ 2 ] ) ?
    					left :
    					( ( left - this.offset.click.left >= containment[ 0 ] ) ?
    						left - o.grid[ 0 ] : left + o.grid[ 0 ] ) ) : left;
    		}
    
    		if ( o.axis === "y" ) {
    			pageX = this.originalPageX;
    		}
    
    		if ( o.axis === "x" ) {
    			pageY = this.originalPageY;
    		}
    	}
    
    	return {
    		top: (
    
    			// The absolute mouse position
    			pageY -
    
    			// Click offset (relative to the element)
    			this.offset.click.top -
    
    			// Only for relative positioned nodes: Relative offset from element to offset parent
    			this.offset.relative.top -
    
    			// The offsetParent's offset without borders (offset + border)
    			this.offset.parent.top +
    			( this.cssPosition === "fixed" ?
    				-this.offset.scroll.top :
    				( scrollIsRootNode ? 0 : this.offset.scroll.top ) )
    		),
    		left: (
    
    			// The absolute mouse position
    			pageX -
    
    			// Click offset (relative to the element)
    			this.offset.click.left -
    
    			// Only for relative positioned nodes: Relative offset from element to offset parent
    			this.offset.relative.left -
    
    			// The offsetParent's offset without borders (offset + border)
    			this.offset.parent.left +
    			( this.cssPosition === "fixed" ?
    				-this.offset.scroll.left :
    				( scrollIsRootNode ? 0 : this.offset.scroll.left ) )
    		)
    	};
    
    };
    
    jQuery.ui.draggable.prototype._mouseStart = function( event ) {
    
    	var o = this.options;
    
    	//Create and append the visible helper
    	this.helper = this._createHelper( event );
    
    	this._addClass( this.helper, "ui-draggable-dragging" );
    
    	//Cache the helper size
    	this._cacheHelperProportions();
    
    	//If ddmanager is used for droppables, set the global draggable
    	if ( jQuery.ui.ddmanager ) {
    		jQuery.ui.ddmanager.current = this;
    	}
    
    	/*
    	 * - Position generation -
    	 * This block generates everything position related - it's the core of draggables.
    	 */
    
    	//Cache the margins of the original element
    	this._cacheMargins();
    
    	//Store the helper's css position
    	this.cssPosition = this.helper.css( "position" );
    	this.scrollParent = this.helper.scrollParent( true );
    	this.offsetParent = this.helper.offsetParent();
    	this.hasFixedAncestor = this.helper.parents().filter( function() {
    			return $( this ).css( "position" ) === "fixed";
    		} ).length > 0;
    
    	//The element's absolute position on the page minus margins
    	this.positionAbs = this.element.offset();
    	this._refreshOffsets( event );
    
    	//Generate the original position
    	this.originalPosition = this.position = this._generatePosition( event, false );
    	this.originalPageX = event.pageX / window.transformScale;
    	this.originalPageY = event.pageY / window.transformScale;
    
    	//Adjust the mouse offset relative to the helper if "cursorAt" is supplied
    	( o.cursorAt && this._adjustOffsetFromHelper( o.cursorAt ) );
    
    	//Set a containment if given in the options
    	this._setContainment();
    
    	//Trigger event + callbacks
    	if ( this._trigger( "start", event ) === false ) {
    		this._clear();
    		return false;
    	}
    
    	//Recache the helper size
    	this._cacheHelperProportions();
    
    	//Prepare the droppable offsets
    	if ( jQuery.ui.ddmanager && !o.dropBehaviour ) {
    		jQuery.ui.ddmanager.prepareOffsets( this, event );
    	}
    
    	// Execute the drag once - this causes the helper not to be visible before getting its
    	// correct position
    	this._mouseDrag( event, true );
    
    	// If the ddmanager is used for droppables, inform the manager that dragging has started
    	// (see #5003)
    	if ( jQuery.ui.ddmanager ) {
    		jQuery.ui.ddmanager.dragStart( this, event );
    	}
    
    	return true;
    
    };
    
    jQuery.ui.draggable.prototype._mouseDrag = function( event, noPropagation ) {
    
    	// reset any necessary cached properties (see #5009)
    	if ( this.hasFixedAncestor ) {
    		this.offset.parent = this._getParentOffset();
    	}
    
    	//Compute the helpers position
    	this.position = this._generatePosition( event, true );
    	this.positionAbs = this._convertPositionTo( "absolute" );
    
    	//Call plugins and callbacks and use the resulting position if something is returned
    	if ( !noPropagation ) {
    		var ui = this._uiHash();
    		if ( this._trigger( "drag", event, ui ) === false ) {
    			this._mouseUp( new $.Event( "mouseup", event ) );
    			return false;
    		}
    		this.position = ui.position;
    	}
    
    	this.helper[ 0 ].style.left = this.position.left + "px";
    	this.helper[ 0 ].style.top = this.position.top + "px";
    
    	if ( jQuery.ui.ddmanager ) {
    		jQuery.ui.ddmanager.drag( this, event );
    	}
    
    	return false;
    };
    
    })();
    body {
    	position:	relative;
    	margin:		0;
    	transform-origin:	0 0 0;
    }
    
    #root {
    	position:	fixed;
    	top:		20px;
    	left:		20px;
    
    	width:		500px;
    	height:		500px;
    
    	border:		solid 2px green;
    
    }
    
    #box {
    	position:	absolute;
    	top:		100px;
    	left:		50px;
    
    	display:	inline-block;
    	width:		50px;
    	height:		50px;
    
    	background:	yellow;
    	border:		solid 2px black;
    }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
    <script>
    /*
     * EXAMPLE CODE
     */
    // this variable is required to make extension work
    window.transformScale = 0.5;
    
    $(document).ready(function() {
      $(document.body).attr('style', 'transform: scale('+ window.transformScale +')');
    
      $('#root').dblclick(function() {
        $('#box').position({
          my: 'right bottom',
          at: 'right bottom',
          of: $('#root')
        });
      })
    
      $('#box').draggable({
        containment: $('#root'),
      });
    
    });
    </script>
    
    <div id="root">
      <div id="box"></div>
    </div>

    【讨论】:

      猜你喜欢
      • 2011-12-09
      • 2015-02-20
      • 2013-06-20
      • 2012-01-27
      • 2012-12-10
      • 2012-11-20
      • 2012-06-03
      • 2014-12-16
      • 1970-01-01
      相关资源
      最近更新 更多