diff options
| -rw-r--r-- | background_scripts/settings.coffee | 1 | ||||
| -rw-r--r-- | content_scripts/scroller.coffee | 83 | ||||
| -rw-r--r-- | content_scripts/vimium_frontend.coffee | 12 | ||||
| -rw-r--r-- | pages/options.coffee | 1 | ||||
| -rw-r--r-- | 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 @@ -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"> |
