From 1f678d685eb39e5269cfe8f87ac99522aa1b5200 Mon Sep 17 00:00:00 2001
From: Stephen Blott
Date: Fri, 7 Nov 2014 10:58:39 +0000
Subject: Smooth scrolling.
---
background_scripts/settings.coffee | 1 +
content_scripts/scroller.coffee | 83 ++++++++++++++++++++++++++++------
content_scripts/vimium_frontend.coffee | 12 ++---
pages/options.coffee | 1 +
pages/options.html | 9 ++++
5 files changed, 85 insertions(+), 21 deletions(-)
diff --git a/background_scripts/settings.coffee b/background_scripts/settings.coffee
index d6e8fcde..f68a51d7 100644
--- a/background_scripts/settings.coffee
+++ b/background_scripts/settings.coffee
@@ -61,6 +61,7 @@ root.Settings = Settings =
# or strings
defaults:
scrollStepSize: 60
+ smoothScroll: false
keyMappings: "# Insert your prefered key mappings here."
linkHintCharacters: "sadfjklewcmpgh"
linkHintNumbers: "0123456789"
diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee
index b3a14c78..4d1109c9 100644
--- a/content_scripts/scroller.coffee
+++ b/content_scripts/scroller.coffee
@@ -5,8 +5,10 @@ window.Scroller = root = {}
# input elements. This mechanism allows us to decide whether to scroll a div or to scroll the whole document.
#
activatedElement = null
+settings = null
-root.init = ->
+root.init = (frontendSettings) ->
+ settings = frontendSettings
handlerStack.push DOMActivate: -> activatedElement = event.target
scrollProperties =
@@ -36,11 +38,13 @@ getDimension = (el, direction, name) ->
ensureScrollChange = (direction, changeFn) ->
axisName = scrollProperties[direction].axisName
element = activatedElement
+ progress = 0
loop
oldScrollValue = element[axisName]
# Elements with `overflow: hidden` should not be scrolled.
overflow = window.getComputedStyle(element).getPropertyValue("overflow-#{direction}")
changeFn(element, axisName) unless overflow == "hidden"
+ progress += element[axisName] - oldScrollValue
break unless element[axisName] == oldScrollValue && element != document.body
# we may have an orphaned element. if so, just scroll the body element.
element = element.parentElement || document.body
@@ -51,6 +55,53 @@ ensureScrollChange = (direction, changeFn) ->
rect = activatedElement.getBoundingClientRect()
if (rect.bottom < 0 || rect.top > window.innerHeight || rect.right < 0 || rect.left > window.innerWidth)
activatedElement = element
+ # Return the amount by which the scroll position has changed.
+ return progress
+
+# Scroll by a relative amount in some direction, possibly smoothly.
+# The constants below seem to roughly match chrome's scroll speeds for both short and long scrolls.
+# TODO(smblott) For very-long scrolls, chrome implements a soft landing; we don't.
+doScrollBy = do ->
+ interval = 10 # Update interval (in ms).
+ duration = 120 # This must be a multiple of interval (also in ms).
+ fudgeFactor = 25
+ timer = null
+
+ clearTimer = ->
+ if timer
+ clearInterval timer
+ timer = null
+
+ # Allow a bit longer for longer scrolls.
+ calculateExtraDuration = (amount) ->
+ extra = fudgeFactor * Math.log Math.abs amount
+ # Ensure we have a multiple of interval.
+ return interval * Math.round (extra / interval)
+
+ scroller = (direction,amount) ->
+ return ensureScrollChange direction, (element, axisName) -> element[axisName] += amount
+
+ (direction,amount,wantSmooth) ->
+ clearTimer()
+
+ unless wantSmooth and settings.get "smoothScroll"
+ scroller direction, amount
+ return
+
+ requiredTicks = (duration + calculateExtraDuration amount) / interval
+ # Round away from 0, so that we don't leave any requested scroll amount unscrolled.
+ rounder = (if 0 <= amount then Math.ceil else Math.floor)
+ delta = rounder(amount / requiredTicks)
+
+ ticks = 0
+ ticker = ->
+ # If we haven't scrolled by the expected amount, then we've hit the top, bottom or side of the activated
+ # element, so stop scrolling.
+ if scroller(direction, delta) != delta or ++ticks == requiredTicks
+ clearTimer()
+
+ timer = setInterval ticker, interval
+ ticker()
# scroll the active element in :direction by :amount * :factor.
# :factor is needed because :amount can take on string values, which scrollBy converts to element dimensions.
@@ -66,26 +117,28 @@ root.scrollBy = (direction, amount, factor = 1) ->
if (!activatedElement || !isRendered(activatedElement))
activatedElement = document.body
- ensureScrollChange direction, (element, axisName) ->
- if Utils.isString amount
- elementAmount = getDimension element, direction, amount
- else
- elementAmount = amount
- elementAmount *= factor
- element[axisName] += elementAmount
+ if Utils.isString amount
+ elementAmount = getDimension activatedElement, direction, amount
+ else
+ elementAmount = amount
+ elementAmount *= factor
+
+ doScrollBy direction, elementAmount, true
-root.scrollTo = (direction, pos) ->
+root.scrollTo = (direction, pos, wantSmooth=false) ->
return unless document.body
if (!activatedElement || !isRendered(activatedElement))
activatedElement = document.body
- ensureScrollChange direction, (element, axisName) ->
- if Utils.isString pos
- elementPos = getDimension element, direction, pos
- else
- elementPos = pos
- element[axisName] = elementPos
+ if Utils.isString pos
+ elementPos = getDimension activatedElement, direction, pos
+ else
+ elementPos = pos
+ axisName = scrollProperties[direction].axisName
+ elementAmount = elementPos - activatedElement[axisName]
+
+ doScrollBy direction, elementAmount, wantSmooth
# TODO refactor and put this together with the code in getVisibleClientRect
isRendered = (element) ->
diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee
index 118f985e..57503565 100644
--- a/content_scripts/vimium_frontend.coffee
+++ b/content_scripts/vimium_frontend.coffee
@@ -49,7 +49,7 @@ settings =
loadedValues: 0
valuesToLoad: ["scrollStepSize", "linkHintCharacters", "linkHintNumbers", "filterLinkHints", "hideHud",
"previousPatterns", "nextPatterns", "findModeRawQuery", "regexFindMode", "userDefinedLinkHintCss",
- "helpDialog_showAdvancedCommands"]
+ "helpDialog_showAdvancedCommands", "smoothScroll"]
isLoaded: false
eventListeners: {}
@@ -101,7 +101,7 @@ initializePreDomReady = ->
settings.addEventListener("load", LinkHints.init.bind(LinkHints))
settings.load()
- Scroller.init()
+ Scroller.init settings
checkIfEnabledForUrl()
@@ -227,10 +227,10 @@ window.focusThisFrame = (shouldHighlight) ->
setTimeout((-> document.body.style.border = borderWas), 200)
extend window,
- scrollToBottom: -> Scroller.scrollTo "y", "max"
- scrollToTop: -> Scroller.scrollTo "y", 0
- scrollToLeft: -> Scroller.scrollTo "x", 0
- scrollToRight: -> Scroller.scrollTo "x", "max"
+ scrollToBottom: -> Scroller.scrollTo "y", "max", true
+ scrollToTop: -> Scroller.scrollTo "y", 0, true
+ scrollToLeft: -> Scroller.scrollTo "x", 0, true
+ scrollToRight: -> Scroller.scrollTo "x", "max", true
scrollUp: -> Scroller.scrollBy "y", -1 * settings.get("scrollStepSize")
scrollDown: -> Scroller.scrollBy "y", settings.get("scrollStepSize")
scrollPageUp: -> Scroller.scrollBy "y", "viewSize", -1/2
diff --git a/pages/options.coffee b/pages/options.coffee
index f5968eb9..3474bcba 100644
--- a/pages/options.coffee
+++ b/pages/options.coffee
@@ -196,6 +196,7 @@ document.addEventListener "DOMContentLoaded", ->
previousPatterns: NonEmptyTextOption
regexFindMode: CheckBoxOption
scrollStepSize: NumberOption
+ smoothScroll: CheckBoxOption
searchEngines: TextOption
searchUrl: NonEmptyTextOption
userDefinedLinkHintCss: TextOption
diff --git a/pages/options.html b/pages/options.html
index 4f037ba5..d6ce2764 100644
--- a/pages/options.html
+++ b/pages/options.html
@@ -359,6 +359,15 @@ unmapAll
+
| Previous patterns |
--
cgit v1.2.3
From 1010b7868eb59dde70bc6faf8fd6fb2969688e48 Mon Sep 17 00:00:00 2001
From: Stephen Blott
Date: Fri, 7 Nov 2014 14:49:54 +0000
Subject: smooth scroll; fix absolute scrolling of active element.
---
content_scripts/scroller.coffee | 47 ++++++++++++++++++++++++-----------------
1 file changed, 28 insertions(+), 19 deletions(-)
diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee
index 4d1109c9..62a930fc 100644
--- a/content_scripts/scroller.coffee
+++ b/content_scripts/scroller.coffee
@@ -23,13 +23,24 @@ scrollProperties =
viewSize: 'clientWidth'
}
-getDimension = (el, direction, name) ->
- # the clientSizes of the body are the dimensions of the entire page, but the viewport should only be the
- # part visible through the window
- if name is 'viewSize' and el is document.body
- if direction is 'x' then window.innerWidth else window.innerHeight
+getDimension = (el, direction, amount) ->
+ if Utils.isString amount
+ name = amount
+ # the clientSizes of the body are the dimensions of the entire page, but the viewport should only be the
+ # part visible through the window
+ if name is 'viewSize' and el is document.body
+ if direction is 'x' then window.innerWidth else window.innerHeight
+ else
+ el[scrollProperties[direction][name]]
else
- el[scrollProperties[direction][name]]
+ amount
+
+# Test whether element should be scrolled.
+isScrollable = (element, direction) ->
+ # Elements with `overflow: hidden` should not be scrolled.
+ overflow = window.getComputedStyle(element).getPropertyValue("overflow-#{direction}")
+ return false if overflow == "hidden"
+ return true
# Chrome does not report scrollHeight accurately for nodes with pseudo-elements of height 0 (bug 110149).
# Therefore we cannot figure out if we have scrolled to the bottom of an element by testing if scrollTop +
@@ -41,9 +52,7 @@ ensureScrollChange = (direction, changeFn) ->
progress = 0
loop
oldScrollValue = element[axisName]
- # Elements with `overflow: hidden` should not be scrolled.
- overflow = window.getComputedStyle(element).getPropertyValue("overflow-#{direction}")
- changeFn(element, axisName) unless overflow == "hidden"
+ changeFn(element, axisName) if isScrollable element, direction
progress += element[axisName] - oldScrollValue
break unless element[axisName] == oldScrollValue && element != document.body
# we may have an orphaned element. if so, just scroll the body element.
@@ -117,10 +126,7 @@ root.scrollBy = (direction, amount, factor = 1) ->
if (!activatedElement || !isRendered(activatedElement))
activatedElement = document.body
- if Utils.isString amount
- elementAmount = getDimension activatedElement, direction, amount
- else
- elementAmount = amount
+ elementAmount = getDimension activatedElement, direction, amount
elementAmount *= factor
doScrollBy direction, elementAmount, true
@@ -131,14 +137,17 @@ root.scrollTo = (direction, pos, wantSmooth=false) ->
if (!activatedElement || !isRendered(activatedElement))
activatedElement = document.body
- if Utils.isString pos
- elementPos = getDimension activatedElement, direction, pos
- else
- elementPos = pos
+ # Find the deepest scrollable element which would move if we scrolled it. This is the element which
+ # ensureScrollChange will scroll.
+ # TODO(smblott) We're pretty much copying what ensureScrollChange does here. Refactor.
+ element = activatedElement
axisName = scrollProperties[direction].axisName
- elementAmount = elementPos - activatedElement[axisName]
+ while element != document.body and
+ (getDimension(element, direction, pos) == element[axisName] or not isScrollable element, direction)
+ element = element.parentElement || document.body
- doScrollBy direction, elementAmount, wantSmooth
+ amount = getDimension(element,direction,pos) - element[axisName]
+ doScrollBy direction, amount, wantSmooth
# TODO refactor and put this together with the code in getVisibleClientRect
isRendered = (element) ->
--
cgit v1.2.3
From b8b1644a306c1fe12b5faa5204630eb30f1e64b3 Mon Sep 17 00:00:00 2001
From: Stephen Blott
Date: Sun, 9 Nov 2014 14:33:46 +0000
Subject: Smooth scroll; handle chrome bug and refactor.
---
content_scripts/scroller.coffee | 125 ++++++++++++++++++++--------------------
1 file changed, 62 insertions(+), 63 deletions(-)
diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee
index 62a930fc..34f9b148 100644
--- a/content_scripts/scroller.coffee
+++ b/content_scripts/scroller.coffee
@@ -36,40 +36,51 @@ getDimension = (el, direction, amount) ->
amount
# Test whether element should be scrolled.
-isScrollable = (element, direction) ->
+isScrollAllowed = (element, direction) ->
# Elements with `overflow: hidden` should not be scrolled.
- overflow = window.getComputedStyle(element).getPropertyValue("overflow-#{direction}")
- return false if overflow == "hidden"
- return true
-
-# Chrome does not report scrollHeight accurately for nodes with pseudo-elements of height 0 (bug 110149).
-# Therefore we cannot figure out if we have scrolled to the bottom of an element by testing if scrollTop +
-# clientHeight == scrollHeight. So just try to increase scrollTop blindly -- if it fails we know we have
-# reached the end of the content.
-ensureScrollChange = (direction, changeFn) ->
+ window.getComputedStyle(element).getPropertyValue("overflow-#{direction}") != "hidden"
+
+# Test whether element actually scrolls 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.
+isScrollPossible = (element, direction, amount, factor) ->
+ axisName = scrollProperties[direction].axisName
+ # delta, 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 case, we're definitely
+ # scrolling backwards, so a delta of -1 will do.
+ delta = factor * getDimension(element, direction, amount) || -1
+ delta = delta / Math.abs delta # 1 or -1
+ before = element[axisName]
+ element[axisName] += delta
+ after = element[axisName]
+ element[axisName] = before
+ before != after
+
+# Find the element we should and can scroll.
+findScrollableElement = (element, direction, amount, factor = 1) ->
axisName = scrollProperties[direction].axisName
- element = activatedElement
- progress = 0
- loop
- oldScrollValue = element[axisName]
- changeFn(element, axisName) if isScrollable element, direction
- progress += element[axisName] - oldScrollValue
- break unless element[axisName] == oldScrollValue && element != document.body
- # we may have an orphaned element. if so, just scroll the body element.
- element = element.parentElement || document.body
-
- # 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.
- rect = activatedElement.getBoundingClientRect()
- if (rect.bottom < 0 || rect.top > window.innerHeight || rect.right < 0 || rect.left > window.innerWidth)
- activatedElement = element
+ while element != document.body and
+ not (isScrollPossible(element, direction, amount, factor) and isScrollAllowed(element, direction))
+ element = element.parentElement || document.body
+ element
+
+performScroll = (element, axisName, amount, checkVisibility = true) ->
+ before = element[axisName]
+ element[axisName] += amount
+
+ if checkVisibility
+ # 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.
+ rect = activatedElement.getBoundingClientRect()
+ if (rect.bottom < 0 || rect.top > window.innerHeight || rect.right < 0 || rect.left > window.innerWidth)
+ activatedElement = element
+
# Return the amount by which the scroll position has changed.
- return progress
+ element[axisName] - before
-# Scroll by a relative amount in some direction, possibly smoothly.
-# The constants below seem to roughly match chrome's scroll speeds for both short and long scrolls.
-# TODO(smblott) For very-long scrolls, chrome implements a soft landing; we don't.
+# Scroll by a relative amount (a number) in some direction, possibly smoothly.
doScrollBy = do ->
interval = 10 # Update interval (in ms).
duration = 120 # This must be a multiple of interval (also in ms).
@@ -87,30 +98,27 @@ doScrollBy = do ->
# Ensure we have a multiple of interval.
return interval * Math.round (extra / interval)
- scroller = (direction,amount) ->
- return ensureScrollChange direction, (element, axisName) -> element[axisName] += amount
-
- (direction,amount,wantSmooth) ->
+ (element, direction, amount, wantSmooth) ->
+ axisName = scrollProperties[direction].axisName
clearTimer()
unless wantSmooth and settings.get "smoothScroll"
- scroller direction, amount
- return
+ return performScroll element, axisName, amount
requiredTicks = (duration + calculateExtraDuration amount) / interval
- # Round away from 0, so that we don't leave any requested scroll amount unscrolled.
- rounder = (if 0 <= amount then Math.ceil else Math.floor)
- delta = rounder(amount / requiredTicks)
+ # Round away from 0, so that we don't leave any scroll amount unscrolled.
+ delta = (if 0 <= amount then Math.ceil else Math.floor)(amount / requiredTicks)
- ticks = 0
- ticker = ->
- # If we haven't scrolled by the expected amount, then we've hit the top, bottom or side of the activated
- # element, so stop scrolling.
- if scroller(direction, delta) != delta or ++ticks == requiredTicks
- clearTimer()
+ if delta
+ ticks = 0
+ ticker = ->
+ if performScroll(element, axisName, delta, false) != delta or ++ticks == requiredTicks
+ # One final call of performScroll to check the visibility of the activated element.
+ performScroll(element, axisName, 0, true)
+ clearTimer()
- timer = setInterval ticker, interval
- ticker()
+ timer = setInterval ticker, interval
+ ticker()
# scroll the active element in :direction by :amount * :factor.
# :factor is needed because :amount can take on string values, which scrollBy converts to element dimensions.
@@ -126,28 +134,19 @@ root.scrollBy = (direction, amount, factor = 1) ->
if (!activatedElement || !isRendered(activatedElement))
activatedElement = document.body
- elementAmount = getDimension activatedElement, direction, amount
- elementAmount *= factor
-
- doScrollBy direction, elementAmount, true
+ element = findScrollableElement activatedElement, direction, amount, factor
+ elementAmount = factor * getDimension element, direction, amount
+ doScrollBy element, direction, elementAmount, true
-root.scrollTo = (direction, pos, wantSmooth=false) ->
+root.scrollTo = (direction, pos, wantSmooth = false) ->
return unless document.body
if (!activatedElement || !isRendered(activatedElement))
activatedElement = document.body
- # Find the deepest scrollable element which would move if we scrolled it. This is the element which
- # ensureScrollChange will scroll.
- # TODO(smblott) We're pretty much copying what ensureScrollChange does here. Refactor.
- element = activatedElement
- axisName = scrollProperties[direction].axisName
- while element != document.body and
- (getDimension(element, direction, pos) == element[axisName] or not isScrollable element, direction)
- element = element.parentElement || document.body
-
- amount = getDimension(element,direction,pos) - element[axisName]
- doScrollBy direction, amount, wantSmooth
+ element = findScrollableElement activatedElement, direction, pos
+ amount = getDimension(element,direction,pos) - element[scrollProperties[direction].axisName]
+ doScrollBy element, direction, amount, wantSmooth
# TODO refactor and put this together with the code in getVisibleClientRect
isRendered = (element) ->
--
cgit v1.2.3
From df521c26fda9b8d3e8c182fc85deaf5b8c723cd4 Mon Sep 17 00:00:00 2001
From: mrmr1993
Date: Mon, 10 Nov 2014 08:27:55 +0000
Subject: Change smooth scrolling to requestAnimationFrame, tidy up scroller
code
---
content_scripts/scroller.coffee | 128 ++++++++++++++++++----------------------
1 file changed, 58 insertions(+), 70 deletions(-)
diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee
index 34f9b148..2e0d08ad 100644
--- a/content_scripts/scroller.coffee
+++ b/content_scripts/scroller.coffee
@@ -1,5 +1,3 @@
-window.Scroller = root = {}
-
#
# activatedElement is different from document.activeElement -- the latter seems to be reserved mostly for
# input elements. This mechanism allows us to decide whether to scroll a div or to scroll the whole document.
@@ -7,10 +5,6 @@ window.Scroller = root = {}
activatedElement = null
settings = null
-root.init = (frontendSettings) ->
- settings = frontendSettings
- handlerStack.push DOMActivate: -> activatedElement = event.target
-
scrollProperties =
x: {
axisName: 'scrollLeft'
@@ -37,8 +31,11 @@ getDimension = (el, direction, amount) ->
# Test whether element should be scrolled.
isScrollAllowed = (element, direction) ->
+ computedStyle = window.getComputedStyle(element)
# Elements with `overflow: hidden` should not be scrolled.
- window.getComputedStyle(element).getPropertyValue("overflow-#{direction}") != "hidden"
+ return computedStyle.getPropertyValue("overflow-#{direction}") != "hidden" and
+ ["hidden", "collapse"].indexOf(computedStyle.getPropertyValue("visibility")) == -1 and
+ computedStyle.getPropertyValue("display") != "none"
# Test whether element actually scrolls 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
@@ -58,7 +55,7 @@ isScrollPossible = (element, direction, amount, factor) ->
before != after
# Find the element we should and can scroll.
-findScrollableElement = (element, direction, amount, factor = 1) ->
+findScrollableElement = (element = document.body, direction, amount, factor = 1) ->
axisName = scrollProperties[direction].axisName
while element != document.body and
not (isScrollPossible(element, direction, amount, factor) and isScrollAllowed(element, direction))
@@ -81,75 +78,66 @@ performScroll = (element, axisName, amount, checkVisibility = true) ->
element[axisName] - before
# Scroll by a relative amount (a number) in some direction, possibly smoothly.
-doScrollBy = do ->
- interval = 10 # Update interval (in ms).
- duration = 120 # This must be a multiple of interval (also in ms).
- fudgeFactor = 25
- timer = null
+doScrollBy = (element, direction, amount, wantSmooth) ->
+ axisName = scrollProperties[direction].axisName
- clearTimer = ->
- if timer
- clearInterval timer
- timer = null
+ unless wantSmooth and settings.get "smoothScroll"
+ return performScroll element, axisName, amount
+
+ duration = 100 # Duration in ms.
+ fudgeFactor = 25
# Allow a bit longer for longer scrolls.
- calculateExtraDuration = (amount) ->
- extra = fudgeFactor * Math.log Math.abs amount
- # Ensure we have a multiple of interval.
- return interval * Math.round (extra / interval)
-
- (element, direction, amount, wantSmooth) ->
- axisName = scrollProperties[direction].axisName
- clearTimer()
-
- unless wantSmooth and settings.get "smoothScroll"
- return performScroll element, axisName, amount
-
- requiredTicks = (duration + calculateExtraDuration amount) / interval
- # Round away from 0, so that we don't leave any scroll amount unscrolled.
- delta = (if 0 <= amount then Math.ceil else Math.floor)(amount / requiredTicks)
-
- if delta
- ticks = 0
- ticker = ->
- if performScroll(element, axisName, delta, false) != delta or ++ticks == requiredTicks
- # One final call of performScroll to check the visibility of the activated element.
- performScroll(element, axisName, 0, true)
- clearTimer()
-
- timer = setInterval ticker, interval
- ticker()
-
-# scroll the active element in :direction by :amount * :factor.
-# :factor is needed because :amount can take on string values, which scrollBy converts to element dimensions.
-root.scrollBy = (direction, amount, factor = 1) ->
- # if this is called before domReady, just use the window scroll function
- if (!document.body and amount instanceof Number)
- if (direction == "x")
- window.scrollBy(amount, 0)
+ duration += fudgeFactor * Math.log Math.abs amount
+
+ roundOut = if 0 <= amount then Math.ceil else Math.floor
+
+ # Round away from 0, so that we don't leave any scroll amount unscrolled.
+ delta = roundOut(amount / duration)
+
+ animatorId = null
+ start = null
+ lastTime = null
+ scrolledAmount = 0
+
+ animate = (timestamp) ->
+ start ?= timestamp
+
+ progress = Math.min(timestamp - start, duration)
+ scrollDelta = roundOut(delta * progress) - scrolledAmount
+ scrolledAmount += scrollDelta
+
+ if performScroll(element, axisName, scrollDelta, false) != scrollDelta or
+ progress >= duration
+ # One final call of performScroll to check the visibility of the activated element.
+ performScroll(element, axisName, 0, true)
+ window.cancelAnimationFrame(animatorId)
else
- window.scrollBy(0, amount)
- return
+ animatorId = window.requestAnimationFrame(animate)
+
+ animatorId = window.requestAnimationFrame(animate)
- if (!activatedElement || !isRendered(activatedElement))
- activatedElement = document.body
+Scroller =
+ init: (frontendSettings) ->
+ settings = frontendSettings
+ handlerStack.push DOMActivate: -> activatedElement = event.target
- element = findScrollableElement activatedElement, direction, amount, factor
- elementAmount = factor * getDimension element, direction, amount
- doScrollBy element, direction, elementAmount, true
+ # scroll the active element in :direction by :amount * :factor.
+ # :factor is needed because :amount can take on string values, which scrollBy converts to element dimensions.
+ scrollBy: (direction, amount, factor = 1) ->
+ # if this is called before domReady, just use the window scroll function
+ return unless document.body
-root.scrollTo = (direction, pos, wantSmooth = false) ->
- return unless document.body
+ element = findScrollableElement activatedElement, direction, amount, factor
+ elementAmount = factor * getDimension element, direction, amount
+ doScrollBy element, direction, elementAmount, true
- if (!activatedElement || !isRendered(activatedElement))
- activatedElement = document.body
+ scrollTo: (direction, pos, wantSmooth = false) ->
+ return unless document.body
- element = findScrollableElement activatedElement, direction, pos
- amount = getDimension(element,direction,pos) - element[scrollProperties[direction].axisName]
- doScrollBy element, direction, amount, wantSmooth
+ element = findScrollableElement activatedElement, direction, pos
+ amount = getDimension(element,direction,pos) - element[scrollProperties[direction].axisName]
+ doScrollBy element, direction, amount, wantSmooth
-# TODO refactor and put this together with the code in getVisibleClientRect
-isRendered = (element) ->
- computedStyle = window.getComputedStyle(element, null)
- return !(computedStyle.getPropertyValue("visibility") != "visible" ||
- computedStyle.getPropertyValue("display") == "none")
+root = exports ? window
+root.Scroller = Scroller
--
cgit v1.2.3
From f577098a9b72d8bde386ee9e558a512dd46442af Mon Sep 17 00:00:00 2001
From: Stephen Blott
Date: Tue, 11 Nov 2014 05:49:04 +0000
Subject: Smooth scroll; options above the fold.
---
pages/options.html | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/pages/options.html b/pages/options.html
index d6ce2764..84953023 100644
--- a/pages/options.html
+++ b/pages/options.html
@@ -282,6 +282,15 @@ unmapAll
|
+
| Previous patterns |
--
cgit v1.2.3
From d19b7dff273674efbde9a3d5f6992bd4aaab41b0 Mon Sep 17 00:00:00 2001
From: Stephen Blott
Date: Tue, 11 Nov 2014 05:56:03 +0000
Subject: Smooth scroll; revert entry logic.
---
content_scripts/scroller.coffee | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee
index 2e0d08ad..1becc523 100644
--- a/content_scripts/scroller.coffee
+++ b/content_scripts/scroller.coffee
@@ -126,14 +126,23 @@ Scroller =
# :factor is needed because :amount can take on string values, which scrollBy converts to element dimensions.
scrollBy: (direction, amount, factor = 1) ->
# if this is called before domReady, just use the window scroll function
- return unless document.body
+ if (!document.body and amount instanceof Number)
+ if (direction == "x")
+ window.scrollBy(amount, 0)
+ else
+ window.scrollBy(0, amount)
+ return
+
+ activatedElement ||= document.body
+ return unless activatedElement
element = findScrollableElement activatedElement, direction, amount, factor
elementAmount = factor * getDimension element, direction, amount
doScrollBy element, direction, elementAmount, true
scrollTo: (direction, pos, wantSmooth = false) ->
- return unless document.body
+ return unless document.body or activatedElement
+ activatedElement ||= document.body
element = findScrollableElement activatedElement, direction, pos
amount = getDimension(element,direction,pos) - element[scrollProperties[direction].axisName]
--
cgit v1.2.3
From 7e807c82bb708ed1e7ed50b2a0c302dda47f1a39 Mon Sep 17 00:00:00 2001
From: Stephen Blott
Date: Tue, 11 Nov 2014 06:52:22 +0000
Subject: Smooth scroll; initial handle keyboard repeat.
---
content_scripts/scroller.coffee | 95 ++++++++++++++++++++++++++++-------------
1 file changed, 66 insertions(+), 29 deletions(-)
diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee
index 1becc523..eba9fafb 100644
--- a/content_scripts/scroller.coffee
+++ b/content_scripts/scroller.coffee
@@ -2,6 +2,7 @@
# activatedElement is different from document.activeElement -- the latter seems to be reserved mostly for
# input elements. This mechanism allows us to decide whether to scroll a div or to scroll the whole document.
#
+root = exports ? window
activatedElement = null
settings = null
@@ -78,44 +79,81 @@ performScroll = (element, axisName, amount, checkVisibility = true) ->
element[axisName] - before
# Scroll by a relative amount (a number) in some direction, possibly smoothly.
-doScrollBy = (element, direction, amount, wantSmooth) ->
- axisName = scrollProperties[direction].axisName
+doScrollBy = do ->
+ time = 0
+ lastActivationId = -1
+ keyupHandler = null
+
+ # When the keyboard is repeating and the key is released, sometimes the last key event is delivered *after*
+ # the keyup event. This could cause indefinite scrolling with no key pressed, because no further keyup
+ # event is delivered (we behave as if the final key is still pressed). To get around this, we briefly hold
+ # a lock to prevent a new smooth scrolling event from beginning too soon after the end of the previous
+ # scroll. This is a hack.
+ keyLock = false
+
+ (element, direction, amount, wantSmooth) ->
+ axisName = scrollProperties[direction].axisName
+
+ unless wantSmooth and settings.get "smoothScroll"
+ return performScroll element, axisName, amount
+
+ # Assumption. If animator A is started before animator B, then A will also finish before B. We need to
+ # verify this - it may not always be true.
+
+ console.log("locked") if keyLock
+ if lastActivationId == time or keyLock
+ # This is a keyboard repeat, and the most-recently activated animator will handle it.
+ return
+
+ if not keyupHandler
+ keyupHandler = root.handlerStack.push keyup: ->
+ time += 1
+ keyLock = true
+ setTimeout((-> keyLock = false), 10)
+ console.log "keyup", time, lastActivationId
+ true # Do not prevent propagation.
- unless wantSmooth and settings.get "smoothScroll"
- return performScroll element, axisName, amount
+ lastActivationId = activationId = ++time
+ console.log activationId
- duration = 100 # Duration in ms.
- fudgeFactor = 25
+ duration = 100 # Duration in ms.
+ fudgeFactor = 25
- # Allow a bit longer for longer scrolls.
- duration += fudgeFactor * Math.log Math.abs amount
+ # Allow a bit longer for longer scrolls.
+ duration += fudgeFactor * Math.log Math.abs amount
- roundOut = if 0 <= amount then Math.ceil else Math.floor
+ # Round away from 0, so that we don't leave any scroll amount unscrolled.
+ roundOut = if 0 <= amount then Math.ceil else Math.floor
+ delta = roundOut(amount / duration)
- # Round away from 0, so that we don't leave any scroll amount unscrolled.
- delta = roundOut(amount / duration)
+ animatorId = null
+ lastTime = null
+ start = null
+ scrolledAmount = 0
- animatorId = null
- start = null
- lastTime = null
- scrolledAmount = 0
+ stopScrolling = (progress) ->
+ if activationId == time
+ # This is the most recent animator, and the key is still pressed.
+ false
+ else
+ duration <= progress
- animate = (timestamp) ->
- start ?= timestamp
+ animate = (timestamp) ->
+ start ?= timestamp
- progress = Math.min(timestamp - start, duration)
- scrollDelta = roundOut(delta * progress) - scrolledAmount
- scrolledAmount += scrollDelta
+ progress = timestamp - start
+ scrollDelta = roundOut(delta * progress) - scrolledAmount
+ scrolledAmount += scrollDelta
- if performScroll(element, axisName, scrollDelta, false) != scrollDelta or
- progress >= duration
- # One final call of performScroll to check the visibility of the activated element.
- performScroll(element, axisName, 0, true)
- window.cancelAnimationFrame(animatorId)
- else
- animatorId = window.requestAnimationFrame(animate)
+ if performScroll(element, axisName, scrollDelta, false) != scrollDelta or stopScrolling progress
+ # One final call of performScroll to check the visibility of the activated element.
+ performScroll(element, axisName, 0, true)
+ window.cancelAnimationFrame(animatorId)
+ time += 1 if activationId == time
+ else
+ animatorId = window.requestAnimationFrame(animate)
- animatorId = window.requestAnimationFrame(animate)
+ animatorId = window.requestAnimationFrame(animate)
Scroller =
init: (frontendSettings) ->
@@ -148,5 +186,4 @@ Scroller =
amount = getDimension(element,direction,pos) - element[scrollProperties[direction].axisName]
doScrollBy element, direction, amount, wantSmooth
-root = exports ? window
root.Scroller = Scroller
--
cgit v1.2.3
From 6bd7add111cbc3a643c786db2ba18c01c886eb97 Mon Sep 17 00:00:00 2001
From: Stephen Blott
Date: Tue, 11 Nov 2014 11:23:17 +0000
Subject: Smooth scroll; tune key-repeat code.
---
content_scripts/scroller.coffee | 37 ++++++++++++++++---------------------
1 file changed, 16 insertions(+), 21 deletions(-)
diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee
index eba9fafb..fc4b922e 100644
--- a/content_scripts/scroller.coffee
+++ b/content_scripts/scroller.coffee
@@ -2,7 +2,6 @@
# activatedElement is different from document.activeElement -- the latter seems to be reserved mostly for
# input elements. This mechanism allows us to decide whether to scroll a div or to scroll the whole document.
#
-root = exports ? window
activatedElement = null
settings = null
@@ -84,12 +83,12 @@ doScrollBy = do ->
lastActivationId = -1
keyupHandler = null
- # When the keyboard is repeating and the key is released, sometimes the last key event is delivered *after*
- # the keyup event. This could cause indefinite scrolling with no key pressed, because no further keyup
- # event is delivered (we behave as if the final key is still pressed). To get around this, we briefly hold
- # a lock to prevent a new smooth scrolling event from beginning too soon after the end of the previous
- # scroll. This is a hack.
- keyLock = false
+ # When the keyboard is repeating and then the key is released, sometimes the last key event is delivered
+ # *after* the keyup event. This could cause indefinite scrolling with no key pressed, because no further
+ # keyup event is delivered (so we behave as if the key is still pressed). To get around this, we briefly
+ # hold a lock; this prevents a new smooth scrolling event from starting too soon after the end of the
+ # previous one.
+ smoothScrollLock = false
(element, direction, amount, wantSmooth) ->
axisName = scrollProperties[direction].axisName
@@ -97,24 +96,18 @@ doScrollBy = do ->
unless wantSmooth and settings.get "smoothScroll"
return performScroll element, axisName, amount
- # Assumption. If animator A is started before animator B, then A will also finish before B. We need to
- # verify this - it may not always be true.
-
- console.log("locked") if keyLock
- if lastActivationId == time or keyLock
- # This is a keyboard repeat, and the most-recently activated animator will handle it.
+ if lastActivationId == time or smoothScrollLock
+ # This is a keyboard repeat (and the most-recently activated animator -- which is still active -- will
+ # keep going), or it's too soon to begin smooth scrolling again.
return
if not keyupHandler
keyupHandler = root.handlerStack.push keyup: ->
time += 1
- keyLock = true
- setTimeout((-> keyLock = false), 10)
- console.log "keyup", time, lastActivationId
- true # Do not prevent propagation.
+ setTimeout((-> smoothScrollLock = false), 50)
+ smoothScrollLock = true # Hereby also returning true, so allowing further propagation of the event.
lastActivationId = activationId = ++time
- console.log activationId
duration = 100 # Duration in ms.
fudgeFactor = 25
@@ -131,9 +124,9 @@ doScrollBy = do ->
start = null
scrolledAmount = 0
- stopScrolling = (progress) ->
+ shouldStopScrolling = (progress) ->
if activationId == time
- # This is the most recent animator, and the key is still pressed.
+ # This is the most recent animator and the key is still pressed, so keep going.
false
else
duration <= progress
@@ -145,10 +138,11 @@ doScrollBy = do ->
scrollDelta = roundOut(delta * progress) - scrolledAmount
scrolledAmount += scrollDelta
- if performScroll(element, axisName, scrollDelta, false) != scrollDelta or stopScrolling progress
+ if performScroll(element, axisName, scrollDelta, false) != scrollDelta or shouldStopScrolling progress
# One final call of performScroll to check the visibility of the activated element.
performScroll(element, axisName, 0, true)
window.cancelAnimationFrame(animatorId)
+ # Advance time if this is the most-recently activated animator and time hasn't otherwise advanced.
time += 1 if activationId == time
else
animatorId = window.requestAnimationFrame(animate)
@@ -186,4 +180,5 @@ Scroller =
amount = getDimension(element,direction,pos) - element[scrollProperties[direction].axisName]
doScrollBy element, direction, amount, wantSmooth
+root = exports ? window
root.Scroller = Scroller
--
cgit v1.2.3
From edc80d03200ecec78f7c30e7c8b0eb41b545cc55 Mon Sep 17 00:00:00 2001
From: Stephen Blott
Date: Tue, 11 Nov 2014 13:31:00 +0000
Subject: Smooth scroll; eliminate race condition.
---
content_scripts/scroller.coffee | 43 ++++++++++++++++++++---------------------
1 file changed, 21 insertions(+), 22 deletions(-)
diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee
index fc4b922e..03be9694 100644
--- a/content_scripts/scroller.coffee
+++ b/content_scripts/scroller.coffee
@@ -77,37 +77,38 @@ performScroll = (element, axisName, amount, checkVisibility = true) ->
# Return the amount by which the scroll position has changed.
element[axisName] - before
+# How scrolling is handled:
+# - For non-smooth 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 (roughly) the same position as two slower taps.
+# - For smooth scrolling with keyboard repeat, the most recently-activated animator continues scrolling
+# until its corresponding keyup event is received. We never initiate a new animator on keyboard repeat.
+
# Scroll by a relative amount (a number) in some direction, possibly smoothly.
doScrollBy = do ->
time = 0
- lastActivationId = -1
- keyupHandler = null
-
- # When the keyboard is repeating and then the key is released, sometimes the last key event is delivered
- # *after* the keyup event. This could cause indefinite scrolling with no key pressed, because no further
- # keyup event is delivered (so we behave as if the key is still pressed). To get around this, we briefly
- # hold a lock; this prevents a new smooth scrolling event from starting too soon after the end of the
- # previous one.
- smoothScrollLock = false
+ mostRecentActivationId = -1
+ lastEvent = null
+ keyHandler = null
(element, direction, amount, wantSmooth) ->
+ if not keyHandler
+ keyHandler = root.handlerStack.push
+ keydown: (event) -> lastEvent = event
+ keyup: -> time += 1
+
axisName = scrollProperties[direction].axisName
unless wantSmooth and settings.get "smoothScroll"
return performScroll element, axisName, amount
- if lastActivationId == time or smoothScrollLock
- # This is a keyboard repeat (and the most-recently activated animator -- which is still active -- will
- # keep going), or it's too soon to begin smooth scrolling again.
+ if mostRecentActivationId == time or lastEvent?.repeat
+ # Either the most-recently activated animator has not yet received its keyup event (so it's still
+ # scrolling), or this is a keyboard repeat (for which we don't initiate a new animator).
return
- if not keyupHandler
- keyupHandler = root.handlerStack.push keyup: ->
- time += 1
- setTimeout((-> smoothScrollLock = false), 50)
- smoothScrollLock = true # Hereby also returning true, so allowing further propagation of the event.
-
- lastActivationId = activationId = ++time
+ mostRecentActivationId = activationId = ++time
duration = 100 # Duration in ms.
fudgeFactor = 25
@@ -126,7 +127,7 @@ doScrollBy = do ->
shouldStopScrolling = (progress) ->
if activationId == time
- # This is the most recent animator and the key is still pressed, so keep going.
+ # This is the most recently-activated animator and we haven't yet seen its keyup event, so keep going.
false
else
duration <= progress
@@ -142,8 +143,6 @@ doScrollBy = do ->
# One final call of performScroll to check the visibility of the activated element.
performScroll(element, axisName, 0, true)
window.cancelAnimationFrame(animatorId)
- # Advance time if this is the most-recently activated animator and time hasn't otherwise advanced.
- time += 1 if activationId == time
else
animatorId = window.requestAnimationFrame(animate)
--
cgit v1.2.3
From a8776fe42db51133205222ef9220485460150bad Mon Sep 17 00:00:00 2001
From: Stephen Blott
Date: Tue, 11 Nov 2014 13:49:25 +0000
Subject: Smooth scroll; tidy up.
---
content_scripts/scroller.coffee | 25 ++++++++++++++-----------
1 file changed, 14 insertions(+), 11 deletions(-)
diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee
index 03be9694..8d6ca128 100644
--- a/content_scripts/scroller.coffee
+++ b/content_scripts/scroller.coffee
@@ -30,16 +30,19 @@ getDimension = (el, direction, amount) ->
amount
# Test whether element should be scrolled.
-isScrollAllowed = (element, direction) ->
+shouldScroll = (element, direction) ->
computedStyle = window.getComputedStyle(element)
# Elements with `overflow: hidden` should not be scrolled.
- return computedStyle.getPropertyValue("overflow-#{direction}") != "hidden" and
- ["hidden", "collapse"].indexOf(computedStyle.getPropertyValue("visibility")) == -1 and
- computedStyle.getPropertyValue("display") != "none"
-
-# Test whether element actually scrolls 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.
+ return false if computedStyle.getPropertyValue("overflow-#{direction}") == "hidden"
+ # Non-visible elements should not be scrolled.
+ return false if ["hidden", "collapse"].indexOf(computedStyle.getPropertyValue("visibility")) != -1
+ 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
+# 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) ->
axisName = scrollProperties[direction].axisName
# delta, here, is treated as a relative amount, which is correct for relative scrolls. For absolute scrolls
@@ -55,10 +58,10 @@ isScrollPossible = (element, direction, amount, factor) ->
before != after
# Find the element we should and can scroll.
-findScrollableElement = (element = document.body, direction, amount, factor = 1) ->
+findScrollableElement = (element, direction, amount, factor = 1) ->
axisName = scrollProperties[direction].axisName
while element != document.body and
- not (isScrollPossible(element, direction, amount, factor) and isScrollAllowed(element, direction))
+ not (isScrollPossible(element, direction, amount, factor) and shouldScroll(element, direction))
element = element.parentElement || document.body
element
@@ -87,7 +90,7 @@ performScroll = (element, axisName, amount, checkVisibility = true) ->
# Scroll by a relative amount (a number) in some direction, possibly smoothly.
doScrollBy = do ->
- time = 0
+ time = 0 # Logical time.
mostRecentActivationId = -1
lastEvent = null
keyHandler = null
--
cgit v1.2.3
From 553a5c21aef68811d7e9289a68edea11613f93ea Mon Sep 17 00:00:00 2001
From: Stephen Blott
Date: Tue, 11 Nov 2014 14:41:58 +0000
Subject: Smooth scroll; add comment and remove unused variable.
---
content_scripts/scroller.coffee | 1 -
1 file changed, 1 deletion(-)
diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee
index 8d6ca128..683e7f99 100644
--- a/content_scripts/scroller.coffee
+++ b/content_scripts/scroller.coffee
@@ -124,7 +124,6 @@ doScrollBy = do ->
delta = roundOut(amount / duration)
animatorId = null
- lastTime = null
start = null
scrolledAmount = 0
--
cgit v1.2.3
From 9ae1561caac47c8f4739492e66e3986f1977a3cb Mon Sep 17 00:00:00 2001
From: Stephen Blott
Date: Tue, 11 Nov 2014 18:54:23 +0000
Subject: Smooth scroll; comments and more minor tidy up.
---
content_scripts/scroller.coffee | 28 +++++++++++++++-------------
content_scripts/vimium_frontend.coffee | 8 ++++----
2 files changed, 19 insertions(+), 17 deletions(-)
diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee
index 683e7f99..33deb5cf 100644
--- a/content_scripts/scroller.coffee
+++ b/content_scripts/scroller.coffee
@@ -95,20 +95,24 @@ doScrollBy = do ->
lastEvent = null
keyHandler = null
- (element, direction, amount, wantSmooth) ->
- if not keyHandler
- keyHandler = root.handlerStack.push
+ (element, direction, amount) ->
+ return unless amount
+
+ unless keyHandler
+ keyHandler = handlerStack.push
keydown: (event) -> lastEvent = event
keyup: -> time += 1
axisName = scrollProperties[direction].axisName
- unless wantSmooth and settings.get "smoothScroll"
+ unless settings.get "smoothScroll"
return performScroll element, axisName, amount
if mostRecentActivationId == time or lastEvent?.repeat
# Either the most-recently activated animator has not yet received its keyup event (so it's still
- # scrolling), or this is a keyboard repeat (for which we don't initiate a new animator).
+ # scrolling), or this is a keyboard repeat (for which we don't initiate a new animator). We need both
+ # of these checks because sometimes (perhaps one time in twenty) the last keyboard repeat arrives
+ # *after* the corresponding keyup.
return
mostRecentActivationId = activationId = ++time
@@ -128,11 +132,9 @@ doScrollBy = do ->
scrolledAmount = 0
shouldStopScrolling = (progress) ->
- if activationId == time
- # This is the most recently-activated animator and we haven't yet seen its keyup event, so keep going.
- false
- else
- duration <= progress
+ # If activationId == time, then this is the most recently-activated animator and we haven't yet seen its
+ # keyup event, so keep going.
+ if activationId == time then false else duration <= progress
animate = (timestamp) ->
start ?= timestamp
@@ -171,15 +173,15 @@ Scroller =
element = findScrollableElement activatedElement, direction, amount, factor
elementAmount = factor * getDimension element, direction, amount
- doScrollBy element, direction, elementAmount, true
+ doScrollBy element, direction, elementAmount
- scrollTo: (direction, pos, wantSmooth = false) ->
+ scrollTo: (direction, pos) ->
return unless document.body or activatedElement
activatedElement ||= document.body
element = findScrollableElement activatedElement, direction, pos
amount = getDimension(element,direction,pos) - element[scrollProperties[direction].axisName]
- doScrollBy element, direction, amount, wantSmooth
+ doScrollBy element, direction, amount
root = exports ? window
root.Scroller = Scroller
diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee
index 57503565..9c59396f 100644
--- a/content_scripts/vimium_frontend.coffee
+++ b/content_scripts/vimium_frontend.coffee
@@ -227,10 +227,10 @@ window.focusThisFrame = (shouldHighlight) ->
setTimeout((-> document.body.style.border = borderWas), 200)
extend window,
- scrollToBottom: -> Scroller.scrollTo "y", "max", true
- scrollToTop: -> Scroller.scrollTo "y", 0, true
- scrollToLeft: -> Scroller.scrollTo "x", 0, true
- scrollToRight: -> Scroller.scrollTo "x", "max", true
+ scrollToBottom: -> Scroller.scrollTo "y", "max"
+ scrollToTop: -> Scroller.scrollTo "y", 0
+ scrollToLeft: -> Scroller.scrollTo "x", 0
+ scrollToRight: -> Scroller.scrollTo "x", "max"
scrollUp: -> Scroller.scrollBy "y", -1 * settings.get("scrollStepSize")
scrollDown: -> Scroller.scrollBy "y", settings.get("scrollStepSize")
scrollPageUp: -> Scroller.scrollBy "y", "viewSize", -1/2
--
cgit v1.2.3
From e9130eb61b7d3742a80e899c0f1afcc23ad7b5ce Mon Sep 17 00:00:00 2001
From: Stephen Blott
Date: Thu, 13 Nov 2014 07:47:45 +0000
Subject: Smooth scrolling; tidy up, better comments.
---
content_scripts/scroller.coffee | 38 +++++++++++++++++++-------------------
1 file changed, 19 insertions(+), 19 deletions(-)
diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee
index 33deb5cf..8ce87253 100644
--- a/content_scripts/scroller.coffee
+++ b/content_scripts/scroller.coffee
@@ -35,7 +35,7 @@ shouldScroll = (element, direction) ->
# Elements with `overflow: hidden` should not be scrolled.
return false if computedStyle.getPropertyValue("overflow-#{direction}") == "hidden"
# Non-visible elements should not be scrolled.
- return false if ["hidden", "collapse"].indexOf(computedStyle.getPropertyValue("visibility")) != -1
+ return false if computedStyle.getPropertyValue("visibility") in ["hidden", "collapse"]
return false if computedStyle.getPropertyValue("display") == "none"
true
@@ -73,25 +73,28 @@ performScroll = (element, axisName, amount, checkVisibility = true) ->
# 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.
+ # TODO(smblott) Refactor this out of here.
rect = activatedElement.getBoundingClientRect()
if (rect.bottom < 0 || rect.top > window.innerHeight || rect.right < 0 || rect.left > window.innerWidth)
activatedElement = element
- # Return the amount by which the scroll position has changed.
- element[axisName] - before
+ # Return true if we successfully scrolled by the requested amount, false otherwise.
+ amount == element[axisName] - before
# How scrolling is handled:
# - For non-smooth 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 (roughly) the same position as two slower taps.
+# scroll to the same position as two slower taps (modulo rounding errors).
# - For smooth scrolling with keyboard repeat, the most recently-activated animator continues scrolling
-# until its corresponding keyup event is received. We never initiate a new animator on keyboard repeat.
+# at least until its corresponding keyup event is received. We never initiate a new animator on keyboard
+# repeat.
# Scroll by a relative amount (a number) in some direction, possibly smoothly.
doScrollBy = do ->
- time = 0 # Logical time.
- mostRecentActivationId = -1
+ # This is logical time. Time is advanced each time an animator is activated, and on each keyup event.
+ time = 0
+ mostRecentActivationTime = -1
lastEvent = null
keyHandler = null
@@ -108,22 +111,19 @@ doScrollBy = do ->
unless settings.get "smoothScroll"
return performScroll element, axisName, amount
- if mostRecentActivationId == time or lastEvent?.repeat
+ if mostRecentActivationTime == time or lastEvent?.repeat
# Either the most-recently activated animator has not yet received its keyup event (so it's still
# scrolling), or this is a keyboard repeat (for which we don't initiate a new animator). We need both
# of these checks because sometimes (perhaps one time in twenty) the last keyboard repeat arrives
# *after* the corresponding keyup.
return
- mostRecentActivationId = activationId = ++time
+ mostRecentActivationTime = activationTime = ++time
- duration = 100 # Duration in ms.
- fudgeFactor = 25
+ # Duration in ms. Allow a bit longer for longer scrolls.
+ duration = 100 + 25 * Math.log Math.abs amount
- # Allow a bit longer for longer scrolls.
- duration += fudgeFactor * Math.log Math.abs amount
-
- # Round away from 0, so that we don't leave any scroll amount unscrolled.
+ # Round away from 0, so that we don't leave any requested scroll amount unscrolled.
roundOut = if 0 <= amount then Math.ceil else Math.floor
delta = roundOut(amount / duration)
@@ -132,9 +132,9 @@ doScrollBy = do ->
scrolledAmount = 0
shouldStopScrolling = (progress) ->
- # If activationId == time, then this is the most recently-activated animator and we haven't yet seen its
- # keyup event, so keep going.
- if activationId == time then false else duration <= progress
+ # If activationTime == time, then this is the most recently-activated animator and we haven't yet seen
+ # its keyup event, so keep going; otherwise, check progress and duration.
+ if activationTime == time then false else duration <= progress
animate = (timestamp) ->
start ?= timestamp
@@ -143,7 +143,7 @@ doScrollBy = do ->
scrollDelta = roundOut(delta * progress) - scrolledAmount
scrolledAmount += scrollDelta
- if performScroll(element, axisName, scrollDelta, false) != scrollDelta or shouldStopScrolling progress
+ if not performScroll(element, axisName, scrollDelta, false) or shouldStopScrolling progress
# One final call of performScroll to check the visibility of the activated element.
performScroll(element, axisName, 0, true)
window.cancelAnimationFrame(animatorId)
--
cgit v1.2.3
From edd0c1ca7495a32930ed1170abaedf2c73234581 Mon Sep 17 00:00:00 2001
From: Stephen Blott
Date: Thu, 13 Nov 2014 08:36:34 +0000
Subject: Smooth scrolling; note incorrect calibration; MUST BE FIXED!
---
content_scripts/scroller.coffee | 3 +++
1 file changed, 3 insertions(+)
diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee
index 8ce87253..07dc9f11 100644
--- a/content_scripts/scroller.coffee
+++ b/content_scripts/scroller.coffee
@@ -139,6 +139,9 @@ doScrollBy = do ->
animate = (timestamp) ->
start ?= timestamp
+ # FIXME(smblott) This calculation is not correctly calibrated:
+ # - A `j` with smooth scrolling does not take you to the same place as a `j` with jump scrolling.
+ # - A `j` followed by `k` does not take you back to exactly where you started.
progress = timestamp - start
scrollDelta = roundOut(delta * progress) - scrolledAmount
scrolledAmount += scrollDelta
--
cgit v1.2.3
From fbf251ee26e528b19afc1b70f80488bc74af0197 Mon Sep 17 00:00:00 2001
From: Stephen Blott
Date: Thu, 13 Nov 2014 12:14:47 +0000
Subject: Smooth scroll; fix displacement and calibrate.
---
content_scripts/scroller.coffee | 87 ++++++++++++++++++++++++++---------------
1 file changed, 55 insertions(+), 32 deletions(-)
diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee
index 07dc9f11..93048907 100644
--- a/content_scripts/scroller.coffee
+++ b/content_scripts/scroller.coffee
@@ -57,7 +57,7 @@ isScrollPossible = (element, direction, amount, factor) ->
element[axisName] = before
before != after
-# Find the element we should and can scroll.
+# Find the element which we should and can scroll (or document.body).
findScrollableElement = (element, direction, amount, factor = 1) ->
axisName = scrollProperties[direction].axisName
while element != document.body and
@@ -85,10 +85,10 @@ performScroll = (element, axisName, amount, checkVisibility = true) ->
# - For non-smooth 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 (modulo rounding errors).
-# - For smooth scrolling with keyboard repeat, 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.
+# 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.
# Scroll by a relative amount (a number) in some direction, possibly smoothly.
doScrollBy = do ->
@@ -103,7 +103,7 @@ doScrollBy = do ->
unless keyHandler
keyHandler = handlerStack.push
- keydown: (event) -> lastEvent = event
+ keydown: -> lastEvent = event
keyup: -> time += 1
axisName = scrollProperties[direction].axisName
@@ -120,40 +120,63 @@ doScrollBy = do ->
mostRecentActivationTime = activationTime = ++time
- # Duration in ms. Allow a bit longer for longer scrolls.
- duration = 100 + 25 * Math.log Math.abs amount
+ isKeyStillDown = ->
+ time == activationTime
+
+ # Store amount's sign and make amount positive; the logic is clearer when amount is positive.
+ sign = amount / Math.abs amount
+ amount = Math.abs amount
- # Round away from 0, so that we don't leave any requested scroll amount unscrolled.
- roundOut = if 0 <= amount then Math.ceil else Math.floor
- delta = roundOut(amount / duration)
+ # Duration in ms. Allow a bit longer for longer scrolls.
+ duration = Math.max 100, 20 * Math.log amount
+ totalDelta = 0
+ calibration = 1.0
+ initialTimestamp = null
+ previousTimestamp = null
animatorId = null
- start = null
- scrolledAmount = 0
- shouldStopScrolling = (progress) ->
- # If activationTime == time, then this is the most recently-activated animator and we haven't yet seen
- # its keyup event, so keep going; otherwise, check progress and duration.
- if activationTime == time then false else duration <= progress
+ progressAnimation = ->
+ animatorId = requestAnimationFrame animate
+
+ cancelAnimation = ->
+ # Make one final call of performScroll to check the visibility of the activated element.
+ performScroll element, axisName, 0, true
+ cancelAnimationFrame animatorId
animate = (timestamp) ->
- start ?= timestamp
-
- # FIXME(smblott) This calculation is not correctly calibrated:
- # - A `j` with smooth scrolling does not take you to the same place as a `j` with jump scrolling.
- # - A `j` followed by `k` does not take you back to exactly where you started.
- progress = timestamp - start
- scrollDelta = roundOut(delta * progress) - scrolledAmount
- scrolledAmount += scrollDelta
-
- if not performScroll(element, axisName, scrollDelta, false) or shouldStopScrolling progress
- # One final call of performScroll to check the visibility of the activated element.
- performScroll(element, axisName, 0, true)
- window.cancelAnimationFrame(animatorId)
+ initialTimestamp ?= timestamp
+ previousTimestamp ?= timestamp
+
+ if timestamp == previousTimestamp
+ return progressAnimation()
+
+ # The elapsed time is typically about 16ms.
+ elapsed = timestamp - previousTimestamp
+ totalElapsed = timestamp - initialTimestamp
+ previousTimestamp = timestamp
+
+ # The constants in the duration calculation, above, are chosen to provide reasonable scroll speeds for
+ # scrolls resulting from distinct keypresses. For continuous scrolls (where the key remains depressed),
+ # some scrolls are too slow, and others too fast. Here, we compensate a bit.
+ if isKeyStillDown() and 50 <= totalElapsed and 0.5 <= calibration <= 1.6
+ calibration *= 1.05 if 1.05 * calibration * amount <= 150 # Speed up slow scrolls.
+ calibration *= 0.95 if 150 <= 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 isKeyStillDown() then delta else Math.max 0, Math.min delta, amount - totalDelta
+
+ if delta and performScroll element, axisName, sign * delta, false
+ totalDelta += delta
+ progressAnimation()
else
- animatorId = window.requestAnimationFrame(animate)
+ # We're done. Make one final call of performScroll to check the visibility of the activated element.
+ performScroll element, axisName, 0, true
+ cancelAnimationFrame animatorId
- animatorId = window.requestAnimationFrame(animate)
+ progressAnimation()
Scroller =
init: (frontendSettings) ->
--
cgit v1.2.3
From c69b2efaabfb5192bbf3f8488649513953fb7344 Mon Sep 17 00:00:00 2001
From: Stephen Blott
Date: Thu, 13 Nov 2014 15:53:28 +0000
Subject: Smooth scroll; yet more refactoring.
---
content_scripts/scroller.coffee | 67 ++++++++++++++++++-----------------------
1 file changed, 30 insertions(+), 37 deletions(-)
diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee
index 93048907..c69ca235 100644
--- a/content_scripts/scroller.coffee
+++ b/content_scripts/scroller.coffee
@@ -29,6 +29,12 @@ getDimension = (el, direction, amount) ->
else
amount
+# Perform a scroll. Return true if we successfully scrolled by the requested amount, and false otherwise.
+performScroll = (element, axisName, amount) ->
+ before = element[axisName]
+ element[axisName] += amount
+ amount == element[axisName] - before
+
# Test whether element should be scrolled.
shouldScroll = (element, direction) ->
computedStyle = window.getComputedStyle(element)
@@ -45,17 +51,13 @@ shouldScroll = (element, direction) ->
# Bug verified in Chrome 38.0.2125.104.
isScrollPossible = (element, direction, amount, factor) ->
axisName = scrollProperties[direction].axisName
- # delta, here, is treated as a relative amount, which is correct for relative scrolls. For absolute scrolls
+ # 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 case, we're definitely
# scrolling backwards, so a delta of -1 will do.
delta = factor * getDimension(element, direction, amount) || -1
delta = delta / Math.abs delta # 1 or -1
- before = element[axisName]
- element[axisName] += delta
- after = element[axisName]
- element[axisName] = before
- before != after
+ performScroll(element, axisName, delta) and performScroll(element, axisName, -delta)
# Find the element which we should and can scroll (or document.body).
findScrollableElement = (element, direction, amount, factor = 1) ->
@@ -65,21 +67,13 @@ findScrollableElement = (element, direction, amount, factor = 1) ->
element = element.parentElement || document.body
element
-performScroll = (element, axisName, amount, checkVisibility = true) ->
- before = element[axisName]
- element[axisName] += amount
-
- if checkVisibility
- # 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.
- # TODO(smblott) Refactor this out of here.
- rect = activatedElement.getBoundingClientRect()
- if (rect.bottom < 0 || rect.top > window.innerHeight || rect.right < 0 || rect.left > window.innerWidth)
- activatedElement = element
-
- # Return true if we successfully scrolled by the requested amount, false otherwise.
- amount == element[axisName] - before
+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.
+ 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.
@@ -109,13 +103,16 @@ doScrollBy = do ->
axisName = scrollProperties[direction].axisName
unless settings.get "smoothScroll"
- return performScroll element, axisName, amount
+ # Jump scrolling.
+ performScroll element, axisName, amount
+ checkVisibility element
+ return
if mostRecentActivationTime == time or lastEvent?.repeat
# Either the most-recently activated animator has not yet received its keyup event (so it's still
- # scrolling), or this is a keyboard repeat (for which we don't initiate a new animator). We need both
- # of these checks because sometimes (perhaps one time in twenty) the last keyboard repeat arrives
- # *after* the corresponding keyup.
+ # scrolling), or this is a keyboard repeat (for which we don't initiate a new animator).
+ # NOTE(smblott) We need both of these checks because sometimes (perhaps one time in twenty) the last
+ # keyboard repeat arrives *after* the corresponding keyup.
return
mostRecentActivationTime = activationTime = ++time
@@ -131,29 +128,26 @@ doScrollBy = do ->
duration = Math.max 100, 20 * Math.log amount
totalDelta = 0
+ totalElapsed = 0.0
calibration = 1.0
- initialTimestamp = null
previousTimestamp = null
animatorId = null
- progressAnimation = ->
+ advanceAnimation = ->
animatorId = requestAnimationFrame animate
cancelAnimation = ->
- # Make one final call of performScroll to check the visibility of the activated element.
- performScroll element, axisName, 0, true
cancelAnimationFrame animatorId
animate = (timestamp) ->
- initialTimestamp ?= timestamp
previousTimestamp ?= timestamp
if timestamp == previousTimestamp
- return progressAnimation()
+ return advanceAnimation()
# The elapsed time is typically about 16ms.
elapsed = timestamp - previousTimestamp
- totalElapsed = timestamp - initialTimestamp
+ totalElapsed += elapsed
previousTimestamp = timestamp
# The constants in the duration calculation, above, are chosen to provide reasonable scroll speeds for
@@ -168,15 +162,14 @@ doScrollBy = do ->
delta = Math.ceil amount * (elapsed / duration) * calibration
delta = if isKeyStillDown() then delta else Math.max 0, Math.min delta, amount - totalDelta
- if delta and performScroll element, axisName, sign * delta, false
+ if delta and performScroll element, axisName, sign * delta
totalDelta += delta
- progressAnimation()
+ advanceAnimation()
else
- # We're done. Make one final call of performScroll to check the visibility of the activated element.
- performScroll element, axisName, 0, true
+ checkVisibility element
cancelAnimationFrame animatorId
- progressAnimation()
+ advanceAnimation()
Scroller =
init: (frontendSettings) ->
--
cgit v1.2.3
From 3816ff9438a73233bdea2512f51439306a1be156 Mon Sep 17 00:00:00 2001
From: Stephen Blott
Date: Thu, 13 Nov 2014 21:37:04 +0000
Subject: Smooth scroll; admit count prefixes.
---
content_scripts/scroller.coffee | 11 +++--------
1 file changed, 3 insertions(+), 8 deletions(-)
diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee
index c69ca235..ed024724 100644
--- a/content_scripts/scroller.coffee
+++ b/content_scripts/scroller.coffee
@@ -88,7 +88,6 @@ checkVisibility = (element) ->
doScrollBy = do ->
# This is logical time. Time is advanced each time an animator is activated, and on each keyup event.
time = 0
- mostRecentActivationTime = -1
lastEvent = null
keyHandler = null
@@ -108,14 +107,10 @@ doScrollBy = do ->
checkVisibility element
return
- if mostRecentActivationTime == time or lastEvent?.repeat
- # Either the most-recently activated animator has not yet received its keyup event (so it's still
- # scrolling), or this is a keyboard repeat (for which we don't initiate a new animator).
- # NOTE(smblott) We need both of these checks because sometimes (perhaps one time in twenty) the last
- # keyboard repeat arrives *after* the corresponding keyup.
- return
+ # We don't activate new animators on keyboard repeats.
+ return if lastEvent?.repeat
- mostRecentActivationTime = activationTime = ++time
+ activationTime = ++time
isKeyStillDown = ->
time == activationTime
--
cgit v1.2.3
From 59cc099d5be454aabbb46ac3bd0ac30c7ab7c0ff Mon Sep 17 00:00:00 2001
From: Stephen Blott
Date: Fri, 14 Nov 2014 05:20:57 +0000
Subject: Smooth scroll; incororate feedback from @mrmr1993.
---
content_scripts/scroller.coffee | 30 +++++++++++++-----------------
1 file changed, 13 insertions(+), 17 deletions(-)
diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee
index ed024724..a1f5bfa1 100644
--- a/content_scripts/scroller.coffee
+++ b/content_scripts/scroller.coffee
@@ -30,7 +30,8 @@ getDimension = (el, direction, amount) ->
amount
# Perform a scroll. Return true if we successfully scrolled by the requested amount, and false otherwise.
-performScroll = (element, axisName, amount) ->
+performScroll = (element, direction, amount) ->
+ axisName = scrollProperties[direction].axisName
before = element[axisName]
element[axisName] += amount
amount == element[axisName] - before
@@ -50,18 +51,16 @@ shouldScroll = (element, direction) ->
# 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) ->
- axisName = scrollProperties[direction].axisName
# 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 case, we're definitely
- # scrolling backwards, so a delta of -1 will do.
+ # 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.
delta = factor * getDimension(element, direction, amount) || -1
- delta = delta / Math.abs delta # 1 or -1
- performScroll(element, axisName, delta) and performScroll(element, axisName, -delta)
+ 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) ->
- axisName = scrollProperties[direction].axisName
while element != document.body and
not (isScrollPossible(element, direction, amount, factor) and shouldScroll(element, direction))
element = element.parentElement || document.body
@@ -94,16 +93,13 @@ doScrollBy = do ->
(element, direction, amount) ->
return unless amount
- unless keyHandler
- keyHandler = handlerStack.push
- keydown: -> lastEvent = event
- keyup: -> time += 1
-
- axisName = scrollProperties[direction].axisName
+ keyHandler ?= handlerStack.push
+ keydown: -> lastEvent = event
+ keyup: -> time += 1
unless settings.get "smoothScroll"
# Jump scrolling.
- performScroll element, axisName, amount
+ performScroll element, direction, amount
checkVisibility element
return
@@ -116,7 +112,7 @@ doScrollBy = do ->
time == activationTime
# Store amount's sign and make amount positive; the logic is clearer when amount is positive.
- sign = amount / Math.abs amount
+ sign = Math.sign amount
amount = Math.abs amount
# Duration in ms. Allow a bit longer for longer scrolls.
@@ -157,12 +153,12 @@ doScrollBy = do ->
delta = Math.ceil amount * (elapsed / duration) * calibration
delta = if isKeyStillDown() then delta else Math.max 0, Math.min delta, amount - totalDelta
- if delta and performScroll element, axisName, sign * delta
+ if delta and performScroll element, direction, sign * delta
totalDelta += delta
advanceAnimation()
else
checkVisibility element
- cancelAnimationFrame animatorId
+ cancelAnimation()
advanceAnimation()
--
cgit v1.2.3
From e12b9422580c898af381ba97fa9806bfe3264e96 Mon Sep 17 00:00:00 2001
From: Stephen Blott
Date: Fri, 14 Nov 2014 08:40:03 +0000
Subject: Smooth scroll; partial fix to race condition.
---
content_scripts/scroller.coffee | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee
index a1f5bfa1..ecb739ec 100644
--- a/content_scripts/scroller.coffee
+++ b/content_scripts/scroller.coffee
@@ -89,13 +89,18 @@ doScrollBy = do ->
time = 0
lastEvent = null
keyHandler = null
+ keyIsDown = false
(element, direction, amount) ->
return unless amount
keyHandler ?= handlerStack.push
- keydown: -> lastEvent = event
- keyup: -> time += 1
+ keydown: ->
+ keyIsDown = true unless event.repeat
+ lastEvent = event
+ keyup: ->
+ keyIsDown = false
+ time += 1
unless settings.get "smoothScroll"
# Jump scrolling.
@@ -109,7 +114,7 @@ doScrollBy = do ->
activationTime = ++time
isKeyStillDown = ->
- time == activationTime
+ time == activationTime and keyIsDown
# Store amount's sign and make amount positive; the logic is clearer when amount is positive.
sign = Math.sign amount
--
cgit v1.2.3
From 9bb2b1b6db5d7276408857d9032d2a9c1b0fe9dd Mon Sep 17 00:00:00 2001
From: Stephen Blott
Date: Fri, 14 Nov 2014 12:01:39 +0000
Subject: Smooth scroll; move doScrollBy to it's own object.
The main reason for doing this is so that we can add the (now)
CoreScroller callbacks to the handler stack earlier, and therefore track
@keyIsDown correctly at startup, while also keeping local state local.
---
content_scripts/scroller.coffee | 54 ++++++++++++++++++++---------------------
1 file changed, 26 insertions(+), 28 deletions(-)
diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee
index ecb739ec..e418a930 100644
--- a/content_scripts/scroller.coffee
+++ b/content_scripts/scroller.coffee
@@ -3,7 +3,6 @@
# input elements. This mechanism allows us to decide whether to scroll a div or to scroll the whole document.
#
activatedElement = null
-settings = null
scrollProperties =
x: {
@@ -83,38 +82,37 @@ checkVisibility = (element) ->
# continues scrolling at least until its corresponding keyup event is received. We never initiate a new
# animator on keyboard repeat.
-# Scroll by a relative amount (a number) in some direction, possibly smoothly.
-doScrollBy = do ->
- # This is logical time. Time is advanced each time an animator is activated, and on each keyup event.
- time = 0
- lastEvent = null
- keyHandler = null
- keyIsDown = false
-
- (element, direction, amount) ->
+CoreScroller =
+ init: (frontendSettings) ->
+ @settings = frontendSettings
+ @time = 0
+ @lastEvent = null
+ @keyIsDown = false
+ handlerStack.push
+ keydown: (event) =>
+ @keyIsDown = true
+ @lastEvent = event
+ keyup: =>
+ @keyIsDown = false
+ @time += 1
+
+ # Scroll element by a relative amount (a number) in some direction.
+ scroll: (element, direction, amount) ->
return unless amount
- keyHandler ?= handlerStack.push
- keydown: ->
- keyIsDown = true unless event.repeat
- lastEvent = event
- keyup: ->
- keyIsDown = false
- time += 1
-
- unless settings.get "smoothScroll"
+ unless @settings.get "smoothScroll"
# Jump scrolling.
performScroll element, direction, amount
checkVisibility element
return
# We don't activate new animators on keyboard repeats.
- return if lastEvent?.repeat
+ return if @lastEvent?.repeat
- activationTime = ++time
+ activationTime = ++@time
- isKeyStillDown = ->
- time == activationTime and keyIsDown
+ isMyKeyStillDown = =>
+ @time == activationTime and @keyIsDown
# Store amount's sign and make amount positive; the logic is clearer when amount is positive.
sign = Math.sign amount
@@ -149,14 +147,14 @@ doScrollBy = do ->
# The constants in the duration calculation, above, are chosen to provide reasonable scroll speeds for
# scrolls resulting from distinct keypresses. For continuous scrolls (where the key remains depressed),
# some scrolls are too slow, and others too fast. Here, we compensate a bit.
- if isKeyStillDown() and 50 <= totalElapsed and 0.5 <= calibration <= 1.6
+ if isMyKeyStillDown() and 50 <= totalElapsed and 0.5 <= calibration <= 1.6
calibration *= 1.05 if 1.05 * calibration * amount <= 150 # Speed up slow scrolls.
calibration *= 0.95 if 150 <= 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 isKeyStillDown() then delta else Math.max 0, Math.min delta, amount - totalDelta
+ delta = if isMyKeyStillDown() then delta else Math.max 0, Math.min delta, amount - totalDelta
if delta and performScroll element, direction, sign * delta
totalDelta += delta
@@ -169,8 +167,8 @@ doScrollBy = do ->
Scroller =
init: (frontendSettings) ->
- settings = frontendSettings
handlerStack.push DOMActivate: -> activatedElement = event.target
+ CoreScroller.init frontendSettings
# scroll the active element in :direction by :amount * :factor.
# :factor is needed because :amount can take on string values, which scrollBy converts to element dimensions.
@@ -188,7 +186,7 @@ Scroller =
element = findScrollableElement activatedElement, direction, amount, factor
elementAmount = factor * getDimension element, direction, amount
- doScrollBy element, direction, elementAmount
+ CoreScroller.scroll element, direction, elementAmount
scrollTo: (direction, pos) ->
return unless document.body or activatedElement
@@ -196,7 +194,7 @@ Scroller =
element = findScrollableElement activatedElement, direction, pos
amount = getDimension(element,direction,pos) - element[scrollProperties[direction].axisName]
- doScrollBy element, direction, amount
+ CoreScroller.scroll element, direction, amount
root = exports ? window
root.Scroller = Scroller
--
cgit v1.2.3
From ce61f39716665e93f510b40580ec8dd046a84ffd Mon Sep 17 00:00:00 2001
From: Stephen Blott
Date: Fri, 14 Nov 2014 13:26:49 +0000
Subject: Smooth scroll; better documentation for calibration.
---
content_scripts/scroller.coffee | 38 ++++++++++++++++++++------------------
1 file changed, 20 insertions(+), 18 deletions(-)
diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee
index e418a930..1e5bac65 100644
--- a/content_scripts/scroller.coffee
+++ b/content_scripts/scroller.coffee
@@ -33,7 +33,7 @@ performScroll = (element, direction, amount) ->
axisName = scrollProperties[direction].axisName
before = element[axisName]
element[axisName] += amount
- amount == element[axisName] - before
+ element[axisName] == amount + before
# Test whether element should be scrolled.
shouldScroll = (element, direction) ->
@@ -88,6 +88,7 @@ CoreScroller =
@time = 0
@lastEvent = null
@keyIsDown = false
+
handlerStack.push
keydown: (event) =>
@keyIsDown = true
@@ -96,6 +97,14 @@ CoreScroller =
@keyIsDown = false
@time += 1
+ # 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.
+
# Scroll element by a relative amount (a number) in some direction.
scroll: (element, direction, amount) ->
return unless amount
@@ -110,9 +119,7 @@ CoreScroller =
return if @lastEvent?.repeat
activationTime = ++@time
-
- isMyKeyStillDown = =>
- @time == activationTime and @keyIsDown
+ isMyKeyStillDown = => @time == activationTime and @keyIsDown
# Store amount's sign and make amount positive; the logic is clearer when amount is positive.
sign = Math.sign amount
@@ -127,17 +134,12 @@ CoreScroller =
previousTimestamp = null
animatorId = null
- advanceAnimation = ->
- animatorId = requestAnimationFrame animate
+ advanceAnimation = -> animatorId = requestAnimationFrame animate
+ cancelAnimation = -> cancelAnimationFrame animatorId
- cancelAnimation = ->
- cancelAnimationFrame animatorId
-
- animate = (timestamp) ->
+ animate = (timestamp) =>
previousTimestamp ?= timestamp
-
- if timestamp == previousTimestamp
- return advanceAnimation()
+ return advanceAnimation() if timestamp == previousTimestamp
# The elapsed time is typically about 16ms.
elapsed = timestamp - previousTimestamp
@@ -145,11 +147,11 @@ CoreScroller =
previousTimestamp = timestamp
# The constants in the duration calculation, above, are chosen to provide reasonable scroll speeds for
- # scrolls resulting from distinct keypresses. For continuous scrolls (where the key remains depressed),
- # some scrolls are too slow, and others too fast. Here, we compensate a bit.
- if isMyKeyStillDown() and 50 <= totalElapsed and 0.5 <= calibration <= 1.6
- calibration *= 1.05 if 1.05 * calibration * amount <= 150 # Speed up slow scrolls.
- calibration *= 0.95 if 150 <= 0.95 * calibration * amount # Slow down fast scrolls.
+ # 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
+ 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.
--
cgit v1.2.3
From fbd487a7387f4ef568313d818cb88946d130d9b2 Mon Sep 17 00:00:00 2001
From: Stephen Blott
Date: Fri, 14 Nov 2014 15:06:50 +0000
Subject: Smooth scroller; move calibration constants out of init.
---
content_scripts/scroller.coffee | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee
index 1e5bac65..ab9be22c 100644
--- a/content_scripts/scroller.coffee
+++ b/content_scripts/scroller.coffee
@@ -97,13 +97,13 @@ CoreScroller =
@keyIsDown = false
@time += 1
- # 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.
+ # 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.
# Scroll element by a relative amount (a number) in some direction.
scroll: (element, direction, amount) ->
@@ -150,8 +150,8 @@ CoreScroller =
# 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
- 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.
+ 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.
--
cgit v1.2.3
From ed3d40acb350812b7e0dbafd30f2777f04369d86 Mon Sep 17 00:00:00 2001
From: Stephen Blott
Date: Sat, 15 Nov 2014 08:19:04 +0000
Subject: Smooth scroll; optimization and more tidy up.
---
content_scripts/scroller.coffee | 82 +++++++++++++++++++++--------------------
1 file 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
--
cgit v1.2.3
From da6c5387ff89ad523f38881248f947525d0a4535 Mon Sep 17 00:00:00 2001
From: Stephen Blott
Date: Sun, 16 Nov 2014 08:47:15 +0000
Subject: Smooth scroll; fix-ups requested by @philc.
---
background_scripts/settings.coffee | 2 +-
content_scripts/scroller.coffee | 21 ++++++++++++++++-----
2 files changed, 17 insertions(+), 6 deletions(-)
diff --git a/background_scripts/settings.coffee b/background_scripts/settings.coffee
index f68a51d7..4a63a5fb 100644
--- a/background_scripts/settings.coffee
+++ b/background_scripts/settings.coffee
@@ -61,7 +61,7 @@ root.Settings = Settings =
# or strings
defaults:
scrollStepSize: 60
- smoothScroll: false
+ smoothScroll: true
keyMappings: "# Insert your prefered key mappings here."
linkHintCharacters: "sadfjklewcmpgh"
linkHintNumbers: "0123456789"
diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee
index 27744116..0964a289 100644
--- a/content_scripts/scroller.coffee
+++ b/content_scripts/scroller.coffee
@@ -16,12 +16,18 @@ scrollProperties =
viewSize: 'clientWidth'
}
+# Translate a scroll request into a number (which will be interpreted by `scrollBy` as a relative amount, or
+# by `scrollTo` as an absolute amount). :direction must be "x" or "y". :amount may be either a number (in
+# which case it is simply returned) or a string. If :amount is a string, then it is either "max" (meaning the
+# height or width of element), or "viewSize". In both cases, we look up and return the requested amount,
+# either in `element` or in `window`, as appropriate.
getDimension = (el, direction, amount) ->
if Utils.isString amount
name = amount
# the clientSizes of the body are the dimensions of the entire page, but the viewport should only be the
# part visible through the window
if name is 'viewSize' and el is document.body
+ # TODO(smblott) Should we not be returning the width/height of element, here?
if direction is 'x' then window.innerWidth else window.innerHeight
else
el[scrollProperties[direction][name]]
@@ -35,7 +41,7 @@ performScroll = (element, direction, amount) ->
element[axisName] += amount
element[axisName] == amount + before
-# Test whether element should be scrolled.
+# Test whether `element` should be scrolled. E.g. hidden elements should not be scrolled.
shouldScroll = (element, direction) ->
computedStyle = window.getComputedStyle(element)
# Elements with `overflow: hidden` must not be scrolled.
@@ -47,13 +53,15 @@ shouldScroll = (element, direction) ->
# 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).
+# Instead, we scroll the element by 1 or -1 and see if it moved (then put it back). :factor is the factor by
+# which :scrollBy and :scrollTo will later scale the scroll amount. :factor can be negative, so we need it
+# here in order to decide whether we should test a forward scroll or a backward scroll.
# Bug verified in Chrome 38.0.2125.104.
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.
+ # gg, G, and friends), amount can be either a string ("max" or "viewSize") 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)
@@ -82,6 +90,8 @@ checkVisibility = (element) ->
# continues scrolling at least until its keyup event is received. We never initiate a new animator on
# keyboard repeat.
+# CoreScroller contains the core function (scroll) and logic for relative scrolls. All scrolls are ultimately
+# translated to relative scrolls. CoreScroller is not exported.
CoreScroller =
init: (frontendSettings) ->
@settings = frontendSettings
@@ -168,6 +178,7 @@ CoreScroller =
# Launch animator.
requestAnimationFrame animate
+# Scroller contains the two main scroll functions (scrollBy and scrollTo) which are exported to clients.
Scroller =
init: (frontendSettings) ->
handlerStack.push DOMActivate: -> activatedElement = event.target
--
cgit v1.2.3
|