Skip to content

Commit

Permalink
Fix fixed positioning off-by-scrollbar bug
Browse files Browse the repository at this point in the history
  • Loading branch information
Zack Bloom committed Mar 25, 2014
1 parent e5b03bc commit 62cb14e
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 17 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.sass-cache
node_modules/
deps/
.DS_Store
15 changes: 12 additions & 3 deletions coffee/tether.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ if not @Tether?

Tether = @Tether

{getScrollParent, getSize, getOuterSize, getBounds, getOffsetParent, extend, addClass, removeClass, updateClasses, defer, flush} = Tether.Utils
{getScrollParent, getSize, getOuterSize, getBounds, getOffsetParent, extend, addClass, removeClass, updateClasses, defer, flush, getScrollBarSize} = Tether.Utils

within = (a, b, diff=1) ->
a + diff >= b >= a - diff
Expand Down Expand Up @@ -379,7 +379,7 @@ class _Tether
top = targetPos.top + targetOffset.top - offset.top

for module in Tether.modules
ret = module.position.call(@, {left, top, targetAttachment, targetPos, @attachment, elementPos, offset, targetOffset, manualOffset, manualTargetOffset})
ret = module.position.call(@, {left, top, targetAttachment, targetPos, @attachment, elementPos, offset, targetOffset, manualOffset, manualTargetOffset, scrollbarSize})

if not ret? or typeof ret isnt 'object'
continue
Expand All @@ -406,11 +406,19 @@ class _Tether
right: pageXOffset - left - width + innerWidth
}

if document.body.scrollWidth > window.innerWidth
scrollbarSize = @cache 'scrollbar-size', getScrollBarSize
next.viewport.bottom -= scrollbarSize.height

if document.body.scrollHeight > window.innerHeight
scrollbarSize = @cache 'scrollbar-size', getScrollBarSize
next.viewport.right -= scrollbarSize.width

if document.body.style.position not in ['', 'static'] or document.body.parentElement.style.position not in ['', 'static']
# Absolute positioning in the body will be relative to the page, not the 'initial containing block'
next.page.bottom = document.body.scrollHeight - top - height
next.page.right = document.body.scrollWidth - left - width

if @options.optimizations?.moveElement isnt false and not @targetModifier?
offsetParent = @cache 'target-offsetparent', => getOffsetParent @target
offsetPosition = @cache 'target-offsetparent-bounds', -> getBounds offsetParent
Expand Down Expand Up @@ -517,6 +525,7 @@ class _Tether

else if (same.viewport.top or same.viewport.bottom) and (same.viewport.left or same.viewport.right)
css.position = 'fixed'

transcribe same.viewport, position.viewport

else if same.offset? and same.offset.top and same.offset.left
Expand Down
35 changes: 34 additions & 1 deletion coffee/utils.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,39 @@ getBounds = (el) ->
getOffsetParent = (el) ->
el.offsetParent or document.documentElement

getScrollBarSize = ->
inner = document.createElement 'div'
inner.style.width = '100%'
inner.style.height = '200px'

outer = document.createElement 'div'
extend outer.style,
position: 'absolute'
top: 0
left: 0
pointerEvents: 'none'
visibility: 'hidden'
width: '200px'
height: '150px'
overflow: 'hidden'

outer.appendChild inner

document.body.appendChild outer

widthContained = inner.offsetWidth
outer.style.overflow = 'scroll'
widthScroll = inner.offsetWidth

if widthContained is widthScroll
widthScroll = outer.clientWidth

document.body.removeChild outer

width = widthContained - widthScroll

{width, height: width}

extend = (out={}) ->
args = []
Array::push.apply(args, arguments)
Expand Down Expand Up @@ -175,4 +208,4 @@ class Evented
else
i++

@Tether.Utils = {getScrollParent, getBounds, getOffsetParent, extend, addClass, removeClass, hasClass, updateClasses, defer, flush, uniqueId, Evented}
@Tether.Utils = {getScrollParent, getBounds, getOffsetParent, extend, addClass, removeClass, hasClass, updateClasses, defer, flush, uniqueId, Evented, getScrollBarSize}
8 changes: 8 additions & 0 deletions examples/simple/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<link rel="stylesheet" href="../resources/css/base.css" />
<link rel="stylesheet" href="../common/css/style.css" />
<style>
* {
box-sizing: border-box;
}
.target {
border: 5px solid grey;
}
</style>
</head>
<body>
<div class="instructions">Resize the page to see the Tether flip.</div>
Expand Down
17 changes: 13 additions & 4 deletions js/tether.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 35 additions & 2 deletions js/utils.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 48 additions & 6 deletions tether.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
}(this, function(require,exports,module) {

(function() {
var Evented, addClass, defer, deferred, extend, flush, getBounds, getOffsetParent, getOrigin, getScrollParent, hasClass, node, removeClass, uniqueId, updateClasses, zeroPosCache,
var Evented, addClass, defer, deferred, extend, flush, getBounds, getOffsetParent, getOrigin, getScrollBarSize, getScrollParent, hasClass, node, removeClass, uniqueId, updateClasses, zeroPosCache,
__hasProp = {}.hasOwnProperty,
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
__slice = [].slice;
Expand Down Expand Up @@ -123,6 +123,38 @@
return el.offsetParent || document.documentElement;
};

getScrollBarSize = function() {
var inner, outer, width, widthContained, widthScroll;
inner = document.createElement('div');
inner.style.width = '100%';
inner.style.height = '200px';
outer = document.createElement('div');
extend(outer.style, {
position: 'absolute',
top: 0,
left: 0,
pointerEvents: 'none',
visibility: 'hidden',
width: '200px',
height: '150px',
overflow: 'hidden'
});
outer.appendChild(inner);
document.body.appendChild(outer);
widthContained = inner.offsetWidth;
outer.style.overflow = 'scroll';
widthScroll = inner.offsetWidth;
if (widthContained === widthScroll) {
widthScroll = outer.clientWidth;
}
document.body.removeChild(outer);
width = widthContained - widthScroll;
return {
width: width,
height: width
};
};

extend = function(out) {
var args, key, obj, val, _i, _len, _ref;
if (out == null) {
Expand Down Expand Up @@ -305,13 +337,14 @@
defer: defer,
flush: flush,
uniqueId: uniqueId,
Evented: Evented
Evented: Evented,
getScrollBarSize: getScrollBarSize
};

}).call(this);

(function() {
var MIRROR_LR, MIRROR_TB, OFFSET_MAP, Tether, addClass, addOffset, attachmentToOffset, autoToFixedAttachment, defer, extend, flush, getBounds, getOffsetParent, getOuterSize, getScrollParent, getSize, now, offsetToPx, parseAttachment, parseOffset, position, removeClass, tethers, transformKey, updateClasses, within, _Tether, _ref,
var MIRROR_LR, MIRROR_TB, OFFSET_MAP, Tether, addClass, addOffset, attachmentToOffset, autoToFixedAttachment, defer, extend, flush, getBounds, getOffsetParent, getOuterSize, getScrollBarSize, getScrollParent, getSize, now, offsetToPx, parseAttachment, parseOffset, position, removeClass, tethers, transformKey, updateClasses, within, _Tether, _ref,
__slice = [].slice,
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };

Expand All @@ -321,7 +354,7 @@

Tether = this.Tether;

_ref = Tether.Utils, getScrollParent = _ref.getScrollParent, getSize = _ref.getSize, getOuterSize = _ref.getOuterSize, getBounds = _ref.getBounds, getOffsetParent = _ref.getOffsetParent, extend = _ref.extend, addClass = _ref.addClass, removeClass = _ref.removeClass, updateClasses = _ref.updateClasses, defer = _ref.defer, flush = _ref.flush;
_ref = Tether.Utils, getScrollParent = _ref.getScrollParent, getSize = _ref.getSize, getOuterSize = _ref.getOuterSize, getBounds = _ref.getBounds, getOffsetParent = _ref.getOffsetParent, extend = _ref.extend, addClass = _ref.addClass, removeClass = _ref.removeClass, updateClasses = _ref.updateClasses, defer = _ref.defer, flush = _ref.flush, getScrollBarSize = _ref.getScrollBarSize;

within = function(a, b, diff) {
if (diff == null) {
Expand Down Expand Up @@ -743,7 +776,7 @@
};

_Tether.prototype.position = function(flushChanges) {
var elementPos, elementStyle, height, left, manualOffset, manualTargetOffset, module, next, offset, offsetBorder, offsetParent, offsetParentSize, offsetParentStyle, offsetPosition, ret, scrollLeft, scrollTop, side, targetAttachment, targetOffset, targetPos, targetSize, top, width, _i, _j, _len, _len1, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6,
var elementPos, elementStyle, height, left, manualOffset, manualTargetOffset, module, next, offset, offsetBorder, offsetParent, offsetParentSize, offsetParentStyle, offsetPosition, ret, scrollLeft, scrollTop, scrollbarSize, side, targetAttachment, targetOffset, targetPos, targetSize, top, width, _i, _j, _len, _len1, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6,
_this = this;
if (flushChanges == null) {
flushChanges = true;
Expand Down Expand Up @@ -796,7 +829,8 @@
offset: offset,
targetOffset: targetOffset,
manualOffset: manualOffset,
manualTargetOffset: manualTargetOffset
manualTargetOffset: manualTargetOffset,
scrollbarSize: scrollbarSize
});
if ((ret == null) || typeof ret !== 'object') {
continue;
Expand All @@ -818,6 +852,14 @@
right: pageXOffset - left - width + innerWidth
}
};
if (document.body.scrollWidth > window.innerWidth) {
scrollbarSize = this.cache('scrollbar-size', getScrollBarSize);
next.viewport.bottom -= scrollbarSize.height;
}
if (document.body.scrollHeight > window.innerHeight) {
scrollbarSize = this.cache('scrollbar-size', getScrollBarSize);
next.viewport.right -= scrollbarSize.width;
}
if (((_ref3 = document.body.style.position) !== '' && _ref3 !== 'static') || ((_ref4 = document.body.parentElement.style.position) !== '' && _ref4 !== 'static')) {
next.page.bottom = document.body.scrollHeight - top - height;
next.page.right = document.body.scrollWidth - left - width;
Expand Down
2 changes: 1 addition & 1 deletion tether.min.js

Large diffs are not rendered by default.

0 comments on commit 62cb14e

Please sign in to comment.