diff options
| -rw-r--r-- | content_scripts/scroller.coffee | 82 |
1 files changed, 43 insertions, 39 deletions
diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee index ab9be22c..27744116 100644 --- a/content_scripts/scroller.coffee +++ b/content_scripts/scroller.coffee @@ -38,49 +38,49 @@ performScroll = (element, direction, amount) -> # Test whether element should be scrolled. shouldScroll = (element, direction) -> computedStyle = window.getComputedStyle(element) - # Elements with `overflow: hidden` should not be scrolled. + # Elements with `overflow: hidden` must not be scrolled. return false if computedStyle.getPropertyValue("overflow-#{direction}") == "hidden" - # Non-visible elements should not be scrolled. + # Elements which are not visible should not be scrolled. return false if computedStyle.getPropertyValue("visibility") in ["hidden", "collapse"] return false if computedStyle.getPropertyValue("display") == "none" true -# Test whether element actually scrolls in the direction required when asked to do so. Due to chrome bug +# Test whether element does actually scroll in the direction required when asked to do so. Due to chrome bug # 110149, scrollHeight and clientHeight cannot be used to reliably determine whether an element will scroll. # Instead, we scroll the element by 1 or -1 and see if it moved (then put it back). # Bug verified in Chrome 38.0.2125.104. -isScrollPossible = (element, direction, amount, factor) -> - # amount, here, is treated as a relative amount, which is correct for relative scrolls. For absolute scrolls - # (only gg, G, and friends), amount can be either 'max' or zero. In the former case, we're definitely - # scrolling forwards, so any positive value will do for delta. In the latter, we're definitely scrolling - # backwards, so a delta of -1 will do. +doesScroll = (element, direction, amount, factor) -> + # amount is treated as a relative amount, which is correct for relative scrolls. For absolute scrolls (only + # gg, G, and friends), amount can be either 'max' or zero. In the former case, we're definitely scrolling + # forwards, so any positive value will do for delta. In the latter, we're definitely scrolling backwards, + # so a delta of -1 will do. For absolute scrolls, factor is always 1. delta = factor * getDimension(element, direction, amount) || -1 delta = Math.sign delta # 1 or -1 performScroll(element, direction, delta) and performScroll(element, direction, -delta) -# Find the element which we should and can scroll (or document.body). -findScrollableElement = (element, direction, amount, factor = 1) -> +# From element and its parents, find the first which we should scroll and which does scroll. +findScrollableElement = (element, direction, amount, factor) -> while element != document.body and - not (isScrollPossible(element, direction, amount, factor) and shouldScroll(element, direction)) + not (doesScroll(element, direction, amount, factor) and shouldScroll(element, direction)) element = element.parentElement || document.body element checkVisibility = (element) -> - # if the activated element has been scrolled completely offscreen, subsequent changes in its scroll - # position will not provide any more visual feedback to the user. therefore we deactivate it so that - # subsequent scrolls only move the parent element. + # If the activated element has been scrolled completely offscreen, then subsequent changes in its scroll + # position will not provide any more visual feedback to the user. Therefore, we deactivate it so that + # subsequent scrolls affect the parent element. rect = activatedElement.getBoundingClientRect() if (rect.bottom < 0 || rect.top > window.innerHeight || rect.right < 0 || rect.left > window.innerWidth) activatedElement = element -# How scrolling is handled: -# - For non-smooth scrolling, the entire scroll happens immediately. +# How scrolling is handled by CoreScroller. +# - For jump scrolling, the entire scroll happens immediately. # - For smooth scrolling with distinct key presses, a separate animator is initiated for each key press. # Therefore, several animators may be active at the same time. This ensures that two quick taps on `j` # scroll to the same position as two slower taps. # - For smooth scrolling with keyboard repeat (continuous scrolling), the most recently-activated animator -# continues scrolling at least until its corresponding keyup event is received. We never initiate a new -# animator on keyboard repeat. +# continues scrolling at least until its keyup event is received. We never initiate a new animator on +# keyboard repeat. CoreScroller = init: (frontendSettings) -> @@ -97,13 +97,16 @@ CoreScroller = @keyIsDown = false @time += 1 + # Return true if CoreScroller would not initiate a new scroll right now. + wouldNotInitiateScroll: -> @lastEvent?.repeat and @settings.get "smoothScroll" + # Calibration fudge factors for continuous scrolling. The calibration value starts at 1.0. We then # increase it (until it exceeds @maxCalibration) if we guess that the scroll is too slow, or decrease it # (until it is less than @minCalibration) if we guess that the scroll is too fast. The cutoff point for # which guess we make is @calibrationBoundary. We require: 0 < @minCalibration <= 1 <= @maxCalibration. - minCalibration: 0.5 # Controls how much we're willing to slow scrolls down; smaller => more slow down. - maxCalibration: 1.6 # Controls how much we're willing to speed scrolls up; bigger => more speed up. - calibrationBoundary: 150 # Boundary between scrolls which are considered too slow, and those too fast. + minCalibration: 0.5 # Controls how much we're willing to slow scrolls down; smaller means more slow down. + maxCalibration: 1.6 # Controls how much we're willing to speed scrolls up; bigger means more speed up. + calibrationBoundary: 150 # Boundary between scrolls which are considered too slow, or too fast. # Scroll element by a relative amount (a number) in some direction. scroll: (element, direction, amount) -> @@ -115,31 +118,28 @@ CoreScroller = checkVisibility element return - # We don't activate new animators on keyboard repeats. + # We don't activate new animators on keyboard repeats; rather, the most-recently activated animator + # continues scrolling. return if @lastEvent?.repeat activationTime = ++@time - isMyKeyStillDown = => @time == activationTime and @keyIsDown + myKeyIsStillDown = => @time == activationTime and @keyIsDown - # Store amount's sign and make amount positive; the logic is clearer when amount is positive. + # Store amount's sign and make amount positive; the arithmetic is clearer when amount is positive. sign = Math.sign amount amount = Math.abs amount - # Duration in ms. Allow a bit longer for longer scrolls. + # Initial intended scroll duration (in ms). We allow a bit longer for longer scrolls. duration = Math.max 100, 20 * Math.log amount totalDelta = 0 totalElapsed = 0.0 calibration = 1.0 previousTimestamp = null - animatorId = null - - advanceAnimation = -> animatorId = requestAnimationFrame animate - cancelAnimation = -> cancelAnimationFrame animatorId animate = (timestamp) => previousTimestamp ?= timestamp - return advanceAnimation() if timestamp == previousTimestamp + return requestAnimationFrame(animate) if timestamp == previousTimestamp # The elapsed time is typically about 16ms. elapsed = timestamp - previousTimestamp @@ -149,23 +149,24 @@ CoreScroller = # The constants in the duration calculation, above, are chosen to provide reasonable scroll speeds for # distinct keypresses. For continuous scrolls, some scrolls are too slow, and others too fast. Here, we # speed up the slower scrolls, and slow down the faster scrolls. - if isMyKeyStillDown() and 50 <= totalElapsed and @minCalibration <= calibration <= @maxCalibration + if myKeyIsStillDown() and 75 <= totalElapsed and @minCalibration <= calibration <= @maxCalibration calibration *= 1.05 if 1.05 * calibration * amount < @calibrationBoundary # Speed up slow scrolls. calibration *= 0.95 if @calibrationBoundary < 0.95 * calibration * amount # Slow down fast scrolls. # Calculate the initial delta, rounding up to ensure progress. Then, adjust delta to account for the # current scroll state. delta = Math.ceil amount * (elapsed / duration) * calibration - delta = if isMyKeyStillDown() then delta else Math.max 0, Math.min delta, amount - totalDelta + delta = if myKeyIsStillDown() then delta else Math.max 0, Math.min delta, amount - totalDelta if delta and performScroll element, direction, sign * delta totalDelta += delta - advanceAnimation() + requestAnimationFrame animate else + # We're done. checkVisibility element - cancelAnimation() - advanceAnimation() + # Launch animator. + requestAnimationFrame animate Scroller = init: (frontendSettings) -> @@ -186,15 +187,18 @@ Scroller = activatedElement ||= document.body return unless activatedElement - element = findScrollableElement activatedElement, direction, amount, factor - elementAmount = factor * getDimension element, direction, amount - CoreScroller.scroll element, direction, elementAmount + # Avoid the expensive scroll calculation if it will not be used. This reduces costs during smooth, + # continuous scrolls, and is just an optimization. + unless CoreScroller.wouldNotInitiateScroll() + element = findScrollableElement activatedElement, direction, amount, factor + elementAmount = factor * getDimension element, direction, amount + CoreScroller.scroll element, direction, elementAmount scrollTo: (direction, pos) -> return unless document.body or activatedElement activatedElement ||= document.body - element = findScrollableElement activatedElement, direction, pos + element = findScrollableElement activatedElement, direction, pos, 1 amount = getDimension(element,direction,pos) - element[scrollProperties[direction].axisName] CoreScroller.scroll element, direction, amount |
