aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephen Blott2014-11-10 21:43:39 +0000
committerStephen Blott2014-11-10 21:43:39 +0000
commit0516f6af3b84ee15928e2d7a2a6d4c20ca461049 (patch)
tree6718b8d41c3507d8818b01d6a4a9fc65fe1f25b0
parent2687fbe835e447beb875f399c4c150dfe919535e (diff)
parentdf521c26fda9b8d3e8c182fc85deaf5b8c723cd4 (diff)
downloadvimium-0516f6af3b84ee15928e2d7a2a6d4c20ca461049.tar.bz2
Merge branch 'smooth-scrolling-requestAnimationFrame' of github.com:mrmr1993/vimium into mrmr1993-smooth-scrolling-requestAnimationFrame
-rw-r--r--background_scripts/settings.coffee1
-rw-r--r--content_scripts/scroller.coffee183
-rw-r--r--content_scripts/vimium_frontend.coffee12
-rw-r--r--pages/options.coffee1
-rw-r--r--pages/options.html9
5 files changed, 133 insertions, 73 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..2e0d08ad 100644
--- a/content_scripts/scroller.coffee
+++ b/content_scripts/scroller.coffee
@@ -1,13 +1,9 @@
-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.
#
activatedElement = null
-
-root.init = ->
- handlerStack.push DOMActivate: -> activatedElement = event.target
+settings = null
scrollProperties =
x: {
@@ -21,74 +17,127 @@ 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
-# 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) ->
+# Test whether element should be scrolled.
+isScrollAllowed = (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.
+isScrollPossible = (element, direction, amount, factor) ->
axisName = scrollProperties[direction].axisName
- element = activatedElement
- 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"
- 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
-
-# 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)
- else
- window.scrollBy(0, amount)
- return
+ # 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 = 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))
+ element = element.parentElement || document.body
+ element
- if (!activatedElement || !isRendered(activatedElement))
- activatedElement = document.body
+performScroll = (element, axisName, amount, checkVisibility = true) ->
+ before = element[axisName]
+ element[axisName] += amount
- ensureScrollChange direction, (element, axisName) ->
- if Utils.isString amount
- elementAmount = getDimension element, direction, amount
- else
- elementAmount = amount
- elementAmount *= factor
- element[axisName] += elementAmount
+ 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.
+ element[axisName] - before
+
+# Scroll by a relative amount (a number) in some direction, possibly smoothly.
+doScrollBy = (element, direction, amount, wantSmooth) ->
+ axisName = scrollProperties[direction].axisName
+
+ unless wantSmooth and settings.get "smoothScroll"
+ return performScroll element, axisName, amount
-root.scrollTo = (direction, pos) ->
- return unless document.body
+ duration = 100 # Duration in ms.
+ fudgeFactor = 25
- if (!activatedElement || !isRendered(activatedElement))
- activatedElement = document.body
+ # Allow a bit longer for longer scrolls.
+ duration += fudgeFactor * Math.log Math.abs amount
- ensureScrollChange direction, (element, axisName) ->
- if Utils.isString pos
- elementPos = getDimension element, direction, pos
+ 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
- elementPos = pos
- element[axisName] = elementPos
-
-# 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")
+ animatorId = window.requestAnimationFrame(animate)
+
+ animatorId = window.requestAnimationFrame(animate)
+
+Scroller =
+ init: (frontendSettings) ->
+ settings = frontendSettings
+ handlerStack.push DOMActivate: -> activatedElement = event.target
+
+ # 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
+
+ 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
+
+ element = findScrollableElement activatedElement, direction, pos
+ amount = getDimension(element,direction,pos) - element[scrollProperties[direction].axisName]
+ doScrollBy element, direction, amount, wantSmooth
+
+root = exports ? window
+root.Scroller = Scroller
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
@@ -360,6 +360,15 @@ unmapAll
</td>
</tr>
<tr>
+ <td class="caption"></td>
+ <td verticalAlign="top" class="booleanOption">
+ <label>
+ <input id="smoothScroll" type="checkbox"/>
+ Use smooth scrolling.
+ </label>
+ </td>
+ </tr>
+ <tr>
<td class="caption">Previous patterns</td>
<td verticalAlign="top">
<div class="help">