Skip to content

Commit 3a7ff13

Browse files
committed
fix: useIonicHeaderParallax hook
1 parent b64c844 commit 3a7ff13

File tree

12 files changed

+439
-232
lines changed

12 files changed

+439
-232
lines changed

example/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"@types/react-router": "5.1.16",
1616
"@types/react-router-dom": "5.1.8",
1717
"babel-jest": "^26.6.0",
18+
"ionic-react-header-parallax": "^0.0.8",
1819
"ionicons": "5.5.3",
1920
"react": "17.0.2",
2021
"react-dom": "17.0.2",
@@ -62,6 +63,6 @@
6263
},
6364
"description": "An Ionic project",
6465
"devDependencies": {
65-
"jest": "26"
66+
"jest": "26.6.0"
6667
}
6768
}

example/src/components/Lorem.css

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
1-
div {
2-
padding: 8px;
3-
margin-bottom: 12px;
4-
}
5-
6-
p {
1+
.lorem {
72
font-size: 16px;
83
line-height: 22px;
94
color: #8c8c8c;
10-
margin: 0;
115
}

example/src/components/Lorem.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import './Lorem.css'
33
export const Lorem: React.FC<{}> = ({}) => {
44
return (
55
<div>
6-
<p>
6+
<p className="lorem">
77
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's
88
standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make
99
a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting,

example/src/pages/Home.css

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +0,0 @@
1-
* {
2-
--ion-color-primary: #5f468c;
3-
}

example/src/pages/Home.tsx

Lines changed: 210 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import * as React from 'react'
12
import {
23
IonBackButton,
34
IonButton,
@@ -12,9 +13,216 @@ import { Lorem } from '../components/Lorem'
1213
import './Home.css'
1314

1415
const 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) => (

example/yarn.lock

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5934,6 +5934,11 @@ internal-slot@^1.0.3:
59345934
has "^1.0.3"
59355935
side-channel "^1.0.4"
59365936

5937+
ionic-react-header-parallax@^0.0.8:
5938+
version "0.0.8"
5939+
resolved "https://registry.yarnpkg.com/ionic-react-header-parallax/-/ionic-react-header-parallax-0.0.8.tgz#c19c33cb3f917accf4233cc3c4858038a74d0ae5"
5940+
integrity sha512-ZyZyOadXaZqgk7J9zy6YOZDYmW5yOmAh96WCUsE9Uxxhwxz6e2NKk9xBtDjeReoB32CXtNaoEGHFCpoRBzH8WA==
5941+
59375942
ionicons@5.5.3, ionicons@^5.1.2, ionicons@^5.5.3:
59385943
version "5.5.3"
59395944
resolved "https://registry.yarnpkg.com/ionicons/-/ionicons-5.5.3.tgz#33cfc4c8a590ac45895f2b10309ef4586ed244c1"
@@ -6406,7 +6411,7 @@ jest-circus@26.6.0:
64066411
stack-utils "^2.0.2"
64076412
throat "^5.0.0"
64086413

6409-
jest-cli@^26.6.0, jest-cli@^26.6.3:
6414+
jest-cli@^26.6.0:
64106415
version "26.6.3"
64116416
resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-26.6.3.tgz#43117cfef24bc4cd691a174a8796a532e135e92a"
64126417
integrity sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==
@@ -6811,15 +6816,6 @@ jest-worker@^26.5.0, jest-worker@^26.6.2:
68116816
merge-stream "^2.0.0"
68126817
supports-color "^7.0.0"
68136818

6814-
jest@26:
6815-
version "26.6.3"
6816-
resolved "https://registry.yarnpkg.com/jest/-/jest-26.6.3.tgz#40e8fdbe48f00dfa1f0ce8121ca74b88ac9148ef"
6817-
integrity sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q==
6818-
dependencies:
6819-
"@jest/core" "^26.6.3"
6820-
import-local "^3.0.2"
6821-
jest-cli "^26.6.3"
6822-
68236819
jest@26.6.0:
68246820
version "26.6.0"
68256821
resolved "https://registry.yarnpkg.com/jest/-/jest-26.6.0.tgz#546b25a1d8c888569dbbe93cae131748086a4a25"

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
"react": "^17.0.2",
6969
"react-dom": "^17.0.2",
7070
"rimraf": "^3.0.2",
71+
"testing-library__dom": "^7.29.4-beta.1",
7172
"ts-jest": "^26",
7273
"ts-node": "^10.2.0",
7374
"typescript": "^4.3.5"

src/IonHeaderParallax.spec.tsx

Lines changed: 0 additions & 17 deletions
This file was deleted.

src/IonHeaderParallax.tsx

Lines changed: 0 additions & 46 deletions
This file was deleted.

0 commit comments

Comments
 (0)