1
- document . addEventListener ( 'DOMContentLoaded' , initializeParallax ) ;
1
+ function initializeParallax ( clip , forceSticky ) {
2
+ var parallax = clip . querySelectorAll ( '*[parallax]' ) ;
3
+ var parallaxDetails = [ ] ;
4
+ var sticky = forceSticky ;
5
+
6
+ for ( var i = 0 ; i < parallax . length ; i ++ ) {
7
+ var elem = parallax [ i ] ;
8
+ var container = elem . parentNode ;
9
+ if ( getComputedStyle ( container ) . overflow != 'visible' ) {
10
+ console . error ( 'Need non-scrollable container to apply perspective for ' , elem ) ;
11
+ continue ;
12
+ }
13
+ if ( clip && container . parentNode != clip ) {
14
+ console . warn ( 'Currently we only track a single overflow clip, but elements from multiple clips found.' , elem ) ;
15
+ }
16
+ var clip = container . parentNode ;
17
+ if ( getComputedStyle ( clip ) . overflow == 'visible' ) {
18
+ console . error ( 'Parent of sticky container should be scrollable element' , elem ) ;
19
+ }
20
+ // TODO(flackr): optimize to not redo this for the same clip/container.
21
+ var perspectiveElement ;
22
+ if ( sticky || getComputedStyle ( clip ) . webkitOverflowScrolling ) {
23
+ sticky = true ;
24
+ perspectiveElement = container ;
25
+ } else {
26
+ perspectiveElement = clip ;
27
+ container . style . transformStyle = 'preserve-3d' ;
28
+ }
29
+ perspectiveElement . style . perspectiveOrigin = 'bottom right' ;
30
+ perspectiveElement . style . perspective = '1px' ;
31
+ if ( sticky )
32
+ elem . style . position = '-webkit-sticky' ;
33
+ if ( sticky )
34
+ elem . style . top = '0' ;
35
+ elem . style . transformOrigin = 'bottom right' ;
36
+
37
+ // Find the previous and next elements to parallax between.
38
+ var previousCover = parallax [ i ] . previousElementSibling ;
39
+ while ( previousCover && ! previousCover . hasAttribute ( 'parallax-cover' ) )
40
+ previousCover = previousCover . previousElementSibling ;
41
+ var nextCover = parallax [ i ] . nextElementSibling ;
42
+ while ( nextCover && ! nextCover . hasAttribute ( 'parallax-cover' ) )
43
+ nextCover = nextCover . nextElementSibling ;
2
44
3
- function initializeParallax ( ) {
4
- var containers = document . querySelectorAll ( '*[parallax-container]' ) ;
5
- var clip ;
6
- for ( var i = 0 ; i < containers . length ; i ++ ) {
7
- // Maybe optimize to not redo this for the same clip or iterate over clips
8
- // instead of containers.
9
- clip = findOverflowClip ( containers [ i ] ) ;
10
- clip . style . perspectiveOrigin = 'bottom right' ;
11
- clip . style . transformStyle = 'preserve-3d'
12
- clip . style . perspective = '1px' ;
45
+ parallaxDetails . push ( { 'node' : parallax [ i ] ,
46
+ 'top' : parallax [ i ] . offsetTop ,
47
+ 'height' : parallax [ i ] . offsetHeight ,
48
+ 'sticky' : ! ! sticky ,
49
+ 'nextCover' : nextCover ,
50
+ 'previousCover' : previousCover } ) ;
13
51
}
14
- var parallax = document . querySelectorAll ( '*[parallax]' ) ;
52
+
53
+ // Add a scroll listener to hide perspective elements when they should no
54
+ // longer be visible.
15
55
clip . addEventListener ( 'scroll' , function ( ) {
16
- for ( var i = 0 ; i < parallax . length ; i ++ ) {
17
- var container = findContainer ( parallax [ i ] ) ;
56
+ for ( var i = 0 ; i < parallaxDetails . length ; i ++ ) {
57
+ var container = parallaxDetails [ i ] . node . parentNode ;
58
+ var previousCover = parallaxDetails [ i ] . previousCover ;
59
+ var nextCover = parallaxDetails [ i ] . nextCover ;
60
+ var parallaxStart = previousCover ? ( previousCover . offsetTop + previousCover . offsetHeight ) : 0 ;
61
+ var parallaxEnd = nextCover ? nextCover . offsetTop : container . offsetHeight ;
18
62
var threshold = 200 ;
19
- var visible = container . offsetTop - threshold - clip . clientHeight < clip . scrollTop &&
20
- container . offsetTop + container . offsetHeight + threshold > clip . scrollTop ;
63
+ var visible = parallaxStart - threshold - clip . clientHeight < clip . scrollTop &&
64
+ parallaxEnd + threshold > clip . scrollTop ;
21
65
var display = visible ? 'block' : 'none'
22
- if ( parallax [ i ] . style . display != display )
23
- parallax [ i ] . style . display = display ;
66
+ if ( parallaxDetails [ i ] . node . style . display != display )
67
+ parallaxDetails [ i ] . node . style . display = display ;
24
68
}
25
69
} ) ;
26
- var parallaxDetails = [ ] ;
27
- for ( var i = 0 ; i < parallax . length ; i ++ ) {
28
- parallax [ i ] . style . position = 'absolute' ;
29
- parallax [ i ] . style . transformStyle = 'preserve-3d' ;
30
- parallax [ i ] . style . transformOrigin = 'bottom right' ;
31
- parallaxDetails . push ( { 'node' : parallax [ i ] ,
32
- 'height' : parallax [ i ] . offsetHeight } ) ;
33
- }
34
70
window . addEventListener ( 'resize' , onResize . bind ( null , parallaxDetails ) ) ;
35
71
onResize ( parallaxDetails ) ;
72
+ for ( var i = 0 ; i < parallax . length ; i ++ ) {
73
+ parallax [ i ] . parentNode . insertBefore ( parallax [ i ] , parallax [ i ] . parentNode . firstChild ) ;
74
+ }
36
75
}
37
76
38
77
function onResize ( details ) {
39
78
for ( var i = 0 ; i < details . length ; i ++ ) {
40
- var container = findContainer ( details [ i ] . node ) ;
41
- var overflowClip = findOverflowClip ( details [ i ] . node ) ;
42
- var scrollbarWidth = overflowClip . offsetWidth - overflowClip . clientWidth ;
43
- var d2 = details [ i ] . height - overflowClip . clientHeight ;
79
+ var container = details [ i ] . node . parentNode ;
80
+
81
+ var clip = container . parentNode ;
82
+ var previousCover = details [ i ] . previousCover ;
83
+ var nextCover = details [ i ] . nextCover ;
84
+ var parallaxStart = previousCover ? ( previousCover . offsetTop + previousCover . offsetHeight ) : 0 ;
85
+ var parallaxEnd = nextCover ? nextCover . offsetTop : container . offsetHeight ;
86
+ console . log ( 'Parallax from ' + parallaxStart + ' to ' + parallaxEnd ) ;
87
+ var scrollbarWidth = details [ i ] . sticky ? 0 : clip . offsetWidth - clip . clientWidth ;
88
+ var parallaxElem = details [ i ] . sticky ? container : clip ;
89
+ var d2 = details [ i ] . height - clip . clientHeight ;
44
90
45
- var depth = ( details [ i ] . height - container . offsetHeight ) / d2 ;
91
+ var depth = ( details [ i ] . height - parallaxEnd + parallaxStart ) / d2 ;
92
+ if ( details [ i ] . sticky )
93
+ depth = 1.0 / depth ;
46
94
47
95
var scale = 1.0 / ( 1.0 - depth ) ;
48
96
49
- // Ugh! The scrollbar is included in the 'bottom right' perspective origin!
97
+ // The scrollbar is included in the 'bottom right' perspective origin.
50
98
var dx = scrollbarWidth * ( scale - 1 ) ;
51
99
// Offset for the position within the container.
52
- var dy = ( container . offsetHeight - details [ i ] . height ) * scale ;
100
+ var dy = details [ i ] . sticky ?
101
+ - ( clip . scrollHeight - parallaxStart - details [ i ] . height ) * ( 1 - scale ) :
102
+ ( parallaxEnd - details [ i ] . height ) * scale ;
53
103
54
104
details [ i ] . node . style . transform = 'scale(' + ( 1 - depth ) + ') translate3d(' + dx + 'px, ' + dy + 'px, ' + depth + 'px)' ;
55
105
}
56
-
57
- }
58
-
59
- function findContainer ( node ) {
60
- while ( node && ! node . hasAttribute ( 'parallax-container' ) )
61
- node = node . parentNode ;
62
- return node ;
63
- }
64
106
65
- function findOverflowClip ( node ) {
66
- while ( node && getComputedStyle ( node ) . overflow == 'visible' )
67
- node = node . parentNode ;
68
- return node ;
69
107
}
0 commit comments