diff options
| author | Stephen Blott | 2014-11-10 21:43:39 +0000 |
|---|---|---|
| committer | Stephen Blott | 2014-11-10 21:43:39 +0000 |
| commit | 0516f6af3b84ee15928e2d7a2a6d4c20ca461049 (patch) | |
| tree | 6718b8d41c3507d8818b01d6a4a9fc65fe1f25b0 | |
| parent | 2687fbe835e447beb875f399c4c150dfe919535e (diff) | |
| parent | df521c26fda9b8d3e8c182fc85deaf5b8c723cd4 (diff) | |
| download | vimium-0516f6af3b84ee15928e2d7a2a6d4c20ca461049.tar.bz2 | |
Merge branch 'smooth-scrolling-requestAnimationFrame' of github.com:mrmr1993/vimium into mrmr1993-smooth-scrolling-requestAnimationFrame
| -rw-r--r-- | background_scripts/settings.coffee | 1 | ||||
| -rw-r--r-- | content_scripts/scroller.coffee | 183 | ||||
| -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, 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"> |
