aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--background_scripts/settings.coffee1
-rw-r--r--content_scripts/scroller.coffee83
-rw-r--r--content_scripts/vimium_frontend.coffee12
-rw-r--r--pages/options.coffee1
-rw-r--r--pages/options.html9
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
@@ -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">