1+ import * as React from 'react'
12import {
23 IonBackButton ,
34 IonButton ,
@@ -12,9 +13,216 @@ import { Lorem } from '../components/Lorem'
1213import './Home.css'
1314
1415const Home : React . FC = ( ) => {
16+ /** inputs **/
17+ let maximumHeight = 350 // @input
18+ const titleColor = '#AAA'
19+ const expandedColor = '#313131'
20+ const imageUrl = 'https://picsum.photos/1080'
21+
22+ /** styles */
23+ const [ ticking , setTicking ] = React . useState < boolean > ( false )
24+
25+ React . useEffect ( ( ) => {
26+ setTimeout ( async ( ) => {
27+ initElements ( )
28+ } , 200 )
29+ } , [ ] )
30+
31+ const initElements = ( ) => {
32+ // ion-header
33+ const header = document . getElementsByTagName ( 'ion-header' ) [ 0 ]
34+ const parentElement = header . parentElement
35+ if ( ! parentElement ) throw new Error ( 'No IonPage parent element' )
36+
37+ // ion-toolbar
38+ const toolbar = header . querySelector ( 'ion-toolbar' )
39+ if ( ! toolbar ) throw new Error ( 'No <ion-toolbar>' )
40+
41+ // ion-toolbar background
42+ const toolbarShadowRoot = toolbar . shadowRoot
43+ if ( ! toolbarShadowRoot ) throw new Error ( 'No shadow' )
44+ const toolbarBackground = toolbarShadowRoot . querySelector ( '.toolbar-background' ) as HTMLElement
45+
46+ if ( ! toolbarBackground ) throw new Error ( 'No .toolbar-background' )
47+
48+ // ion-title
49+ const ionTitle = toolbar . querySelector ( 'ion-title' )
50+
51+ // ion-buttons
52+ const barButtons = header . querySelector ( 'ion-buttons' )
53+
54+ // ion-content
55+ const ionContent = parentElement . querySelector ( 'ion-content' )
56+ if ( ! ionContent ) throw new Error ( 'Parallax requires an <ion-content> element on the page to work.' )
57+ const scrollContent = ionContent . shadowRoot ?. querySelector ( '.inner-scroll' ) as HTMLElement
58+ if ( ! scrollContent ) {
59+ throw new Error ( 'Parallax directive requires an <ion-content> element on the page to work.' )
60+ }
61+
62+ // create image overly
63+ const imageOverlay = document . createElement ( 'div' )
64+ imageOverlay . classList . add ( 'image-overlay' )
65+ const colorOverlay = document . createElement ( 'div' )
66+ colorOverlay . classList . add ( 'color-overlay' )
67+ colorOverlay . appendChild ( imageOverlay )
68+ header . appendChild ( colorOverlay )
69+
70+ const overlayTitle = ionTitle && ( ionTitle . cloneNode ( true ) as HTMLElement )
71+
72+ if ( overlayTitle ) {
73+ overlayTitle . classList . add ( 'parallax-title' )
74+
75+ setTimeout ( ( ) => {
76+ if ( overlayTitle . shadowRoot ) {
77+ const toolbarTitle = overlayTitle . shadowRoot . querySelector ( '.toolbar-title' ) as HTMLElement
78+ toolbarTitle . style . pointerEvents = 'unset'
79+ }
80+ } , 200 )
81+ }
82+
83+ if ( overlayTitle ) {
84+ imageOverlay . appendChild ( overlayTitle )
85+ }
86+ if ( barButtons ) {
87+ imageOverlay . appendChild ( barButtons )
88+ }
89+
90+ /*** initStyles ***/
91+ // still in init use JS DOM
92+ let headerHeight = scrollContent . clientHeight
93+ setTicking ( false )
94+
95+ // fetch styles
96+ maximumHeight = parseFloat ( maximumHeight . toString ( ) )
97+ let headerMinHeight = toolbar . offsetHeight
98+
99+ let scrollContentPaddingTop : number = parseFloat (
100+ window . getComputedStyle ( scrollContent , null ) . paddingTop . replace ( 'px' , '' )
101+ )
102+
103+ const originalToolbarBgColor = window . getComputedStyle ( toolbarBackground , null ) . backgroundColor
104+ if ( ! originalToolbarBgColor ) {
105+ throw new Error ( 'Error: toolbarBackround is null.' )
106+ }
107+
108+ // header and title
109+ header . style . position = 'relative'
110+
111+ if ( overlayTitle ) {
112+ overlayTitle . style . color = titleColor
113+ overlayTitle . style . position = 'absolute'
114+ overlayTitle . style . width = '100%'
115+ overlayTitle . style . height = '100%'
116+ overlayTitle . style . textAlign = 'center'
117+ }
118+
119+ // color overlay
120+ colorOverlay . style . backgroundColor = originalToolbarBgColor
121+ colorOverlay . style . height = `${ maximumHeight } px`
122+ colorOverlay . style . position = 'absolute'
123+ colorOverlay . style . top = `${ - headerMinHeight * 0 } px`
124+ colorOverlay . style . left = '0'
125+ colorOverlay . style . width = '100%'
126+ colorOverlay . style . zIndex = '10'
127+ colorOverlay . style . pointerEvents = 'none'
128+
129+ // image overlay
130+ imageOverlay . style . backgroundColor = expandedColor
131+ imageOverlay . style . backgroundImage = `url(${ imageUrl } )`
132+ imageOverlay . style . height = '100%'
133+ imageOverlay . style . width = '100%'
134+ imageOverlay . style . pointerEvents = 'none'
135+ imageOverlay . style . backgroundSize = 'cover'
136+ imageOverlay . style . backgroundPosition = 'center'
137+
138+ // .toolbar-background
139+ toolbarBackground . style . backgroundColor = originalToolbarBgColor
140+
141+ // .bar-buttons
142+ if ( barButtons ) {
143+ barButtons . style . pointerEvents = 'all'
144+
145+ Array . from ( barButtons . children ) . forEach ( ( btn ) => {
146+ console . log ( btn , btn as HTMLElement )
147+ const htmlBtn = btn as HTMLElement
148+ htmlBtn . style . color = titleColor
149+ } )
150+ }
151+
152+ // .scroll-content
153+ if ( scrollContent ) {
154+ scrollContent . setAttribute ( 'parallax' , '' )
155+ scrollContent . style . paddingTop = `${ maximumHeight + scrollContentPaddingTop - headerMinHeight } px`
156+ }
157+
158+ /** init events */
159+ window . addEventListener (
160+ 'resize' ,
161+ ( ) => {
162+ headerHeight = scrollContent . clientHeight
163+ } ,
164+ false
165+ )
166+
167+ if ( scrollContent ) {
168+ scrollContent . addEventListener ( 'scroll' , ( e ) => {
169+ if ( ! ticking ) {
170+ window . requestAnimationFrame ( ( ) => {
171+ // to do
172+
173+ const scrollTop = scrollContent . scrollTop
174+ let translateAmt : number
175+ let scaleAmt : number
176+ if ( scrollTop >= 0 ) {
177+ translateAmt = scrollTop / 2
178+ scaleAmt = 1
179+ } else {
180+ translateAmt = 0
181+ scaleAmt = scrollTop / headerHeight + 1
182+ }
183+ // Parallax total progress
184+ headerMinHeight = toolbar . offsetHeight
185+
186+ let progress = ( maximumHeight - scrollTop - headerMinHeight ) / ( maximumHeight - headerMinHeight )
187+ progress = Math . max ( progress , 0 )
188+
189+ let targetHeight = maximumHeight - scrollTop
190+ targetHeight = Math . max ( targetHeight , headerMinHeight )
191+
192+ // .toolbar-background: change color
193+ imageOverlay . style . height = `${ targetHeight } px`
194+ imageOverlay . style . opacity = `${ progress } `
195+ colorOverlay . style . height = `${ targetHeight } px`
196+ colorOverlay . style . opacity = targetHeight > headerMinHeight ? '1' : '0'
197+ toolbarBackground . style . backgroundColor =
198+ targetHeight > headerMinHeight ? 'transparent' : originalToolbarBgColor
199+
200+ // .bar-buttons
201+ if ( barButtons ) {
202+ if ( targetHeight > headerMinHeight ) {
203+ imageOverlay . append ( barButtons )
204+ Array . from ( barButtons . children ) . forEach ( ( btn ) => {
205+ const htmlBtn = btn as HTMLElement
206+ htmlBtn . style . color = titleColor
207+ } )
208+ } else {
209+ toolbar . append ( barButtons )
210+ Array . from ( barButtons . children ) . forEach ( ( btn ) => {
211+ const htmlBtn = btn as HTMLElement
212+ htmlBtn . style . color = 'unset'
213+ } )
214+ }
215+ }
216+ } )
217+ }
218+ setTicking ( true )
219+ } )
220+ }
221+ }
222+
15223 return (
16224 < IonPage >
17- < IonHeader >
225+ < IonHeader translucent >
18226 < IonToolbar mode = "ios" >
19227 < IonButtons slot = "start" >
20228 < IonBackButton defaultHref = "/" />
@@ -26,7 +234,7 @@ const Home: React.FC = () => {
26234 </ IonToolbar >
27235 </ IonHeader >
28236
29- < IonContent fullscreen className = "ion-padding" >
237+ < IonContent className = "ion-padding" >
30238 { Array ( 20 )
31239 . fill ( 1 )
32240 . map ( ( el , index ) => (
0 commit comments