forked from philc/vimium
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathscroller.coffee
89 lines (73 loc) · 3.46 KB
/
scroller.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
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
scrollProperties =
x: {
axisName: 'scrollLeft'
max: 'scrollWidth'
viewSize: 'clientHeight'
}
y: {
axisName: 'scrollTop'
max: 'scrollHeight'
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
else
el[scrollProperties[direction][name]]
# 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) ->
axisName = scrollProperties[direction].axisName
element = activatedElement
loop
oldScrollValue = element[axisName]
changeFn(element, axisName)
lastElement = element
# we may have an orphaned element. if so, just scroll the body element.
element = element.parentElement || document.body
break unless (lastElement[axisName] == oldScrollValue && lastElement != 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 = lastElement
# 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
if (!activatedElement || !isRendered(activatedElement))
activatedElement = document.body
amount = getDimension activatedElement, direction, amount if Utils.isString amount
amount *= factor
if (amount != 0)
ensureScrollChange direction, (element, axisName) -> element[axisName] += amount
root.scrollTo = (direction, pos) ->
return unless document.body
if (!activatedElement || !isRendered(activatedElement))
activatedElement = document.body
pos = getDimension activatedElement, direction, pos if Utils.isString pos
ensureScrollChange direction, (element, axisName) -> element[axisName] = pos
# 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")