-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: fix useIonicHeaderParallax hook
- Loading branch information
Showing
1 changed file
with
121 additions
and
105 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,209 +1,225 @@ | ||
import * as React from 'react' | ||
import * as React from "react"; | ||
|
||
export type UseIonHeaderParallaxInput = { | ||
image: string | ||
expandedColor?: string | ||
titleColor?: string | ||
maximumHeight?: number | ||
} | ||
image: string; | ||
expandedColor?: string; | ||
titleColor?: string; | ||
maximumHeight?: number; | ||
}; | ||
|
||
export type UseIonHeaderParallaxResult = { | ||
ref: React.RefObject<any> | ||
} | ||
ref: React.RefObject<any>; | ||
}; | ||
|
||
export function useIonHeaderParallax({ | ||
image, | ||
titleColor = '#AAA', | ||
expandedColor = '#313131', | ||
titleColor = "#AAA", | ||
expandedColor = "#313131", | ||
maximumHeight = 300, | ||
}: UseIonHeaderParallaxInput): UseIonHeaderParallaxResult { | ||
/** styles */ | ||
const [ticking, setTicking] = React.useState<boolean>(false) | ||
const [ticking, setTicking] = React.useState<boolean>(false); | ||
|
||
const ref: React.RefObject<any> = React.useRef<any>(null) | ||
const ref: React.RefObject<any> = React.useRef<any>(null); | ||
|
||
React.useEffect(() => { | ||
setTimeout(() => { | ||
initElements() | ||
}, 300) | ||
}, [image, titleColor, expandedColor, maximumHeight, ref]) | ||
initElements(); | ||
}, 300); | ||
}, [image, titleColor, expandedColor, maximumHeight, ref]); | ||
|
||
const initElements = () => { | ||
// ion-header | ||
if (ref && ref.current) { | ||
const header = ref.current | ||
const parentElement = header.parentElement | ||
if (!parentElement) throw new Error('No IonPage parent element') | ||
const header = ref.current; | ||
const parentElement = header.parentElement; | ||
if (!parentElement) throw new Error("No IonPage parent element"); | ||
|
||
// ion-toolbar | ||
const toolbar = header.querySelector('ion-toolbar') as HTMLElement | ||
if (!toolbar) throw new Error('No <ion-toolbar>') | ||
const toolbar = header.querySelector("ion-toolbar") as HTMLElement; | ||
if (!toolbar) throw new Error("No <ion-toolbar>"); | ||
|
||
// ion-toolbar background | ||
const toolbarShadowRoot = toolbar.shadowRoot | ||
const toolbarShadowRoot = toolbar.shadowRoot; | ||
|
||
if (!toolbarShadowRoot) throw new Error('No shadow') | ||
const toolbarBackground = toolbarShadowRoot.querySelector('.toolbar-background') as HTMLElement | ||
if (!toolbarShadowRoot) throw new Error("No shadow"); | ||
const toolbarBackground = toolbarShadowRoot.querySelector( | ||
".toolbar-background" | ||
) as HTMLElement; | ||
|
||
// ion-title | ||
const ionTitle = toolbar.querySelector('ion-title') | ||
const ionTitle = toolbar.querySelector("ion-title"); | ||
|
||
// ion-buttons | ||
const barButtons = header.querySelector('ion-buttons') as HTMLElement | ||
const barButtons = header.querySelector("ion-buttons") as HTMLElement; | ||
|
||
// ion-content | ||
const ionContent = parentElement.querySelector('ion-content') | ||
if (!ionContent) throw new Error('Parallax requires an <ion-content> element on the page to work.') | ||
const scrollContent = ionContent.shadowRoot?.querySelector('.inner-scroll') as HTMLElement | ||
const ionContent = parentElement.querySelector("ion-content"); | ||
if (!ionContent) | ||
throw new Error( | ||
"Parallax requires an <ion-content> element on the page to work." | ||
); | ||
const scrollContent = ionContent.shadowRoot?.querySelector( | ||
".inner-scroll" | ||
) as HTMLElement; | ||
if (!scrollContent) { | ||
throw new Error('Parallax directive requires an <ion-content> element on the page to work.') | ||
throw new Error( | ||
"Parallax directive requires an <ion-content> element on the page to work." | ||
); | ||
} | ||
|
||
// create image overly | ||
const imageOverlay = document.createElement('div') | ||
imageOverlay.classList.add('image-overlay') | ||
const colorOverlay = document.createElement('div') | ||
colorOverlay.classList.add('color-overlay') | ||
colorOverlay.appendChild(imageOverlay) | ||
header.appendChild(colorOverlay) | ||
const imageOverlay = document.createElement("div"); | ||
imageOverlay.classList.add("image-overlay"); | ||
const colorOverlay = document.createElement("div"); | ||
colorOverlay.classList.add("color-overlay"); | ||
colorOverlay.appendChild(imageOverlay); | ||
header.appendChild(colorOverlay); | ||
|
||
const overlayTitle = ionTitle && (ionTitle.cloneNode(true) as HTMLElement) | ||
const overlayTitle = | ||
ionTitle && (ionTitle.cloneNode(true) as HTMLElement); | ||
|
||
if (overlayTitle) { | ||
overlayTitle.classList.add('parallax-title') | ||
overlayTitle.classList.add("parallax-title"); | ||
|
||
setTimeout(() => { | ||
if (overlayTitle.shadowRoot) { | ||
const toolbarTitle = overlayTitle.shadowRoot.querySelector('.toolbar-title') as HTMLElement | ||
toolbarTitle.style.pointerEvents = 'unset' | ||
const toolbarTitle = overlayTitle.shadowRoot.querySelector( | ||
".toolbar-title" | ||
) as HTMLElement; | ||
toolbarTitle.style.pointerEvents = "unset"; | ||
} | ||
}, 200) | ||
}, 200); | ||
} | ||
|
||
if (overlayTitle) { | ||
imageOverlay.appendChild(overlayTitle) | ||
imageOverlay.appendChild(overlayTitle); | ||
} | ||
if (barButtons) { | ||
imageOverlay.appendChild(barButtons) | ||
imageOverlay.appendChild(barButtons); | ||
} | ||
|
||
/*** initStyles ***/ | ||
// still in init use JS DOM | ||
setTicking(false) | ||
setTicking(false); | ||
|
||
// fetch styles | ||
maximumHeight = parseFloat(maximumHeight.toString()) | ||
let headerMinHeight = toolbar.offsetHeight | ||
maximumHeight = parseFloat(maximumHeight.toString()); | ||
let headerMinHeight = toolbar.offsetHeight; | ||
|
||
let scrollContentPaddingTop: number = parseFloat( | ||
window.getComputedStyle(scrollContent as Element, null).paddingTop.replace('px', '') | ||
) | ||
|
||
const originalToolbarBgColor = window.getComputedStyle(toolbarBackground as Element, null).backgroundColor | ||
const originalToolbarBgColor = window.getComputedStyle( | ||
toolbarBackground as Element, | ||
null | ||
).backgroundColor; | ||
if (!originalToolbarBgColor) { | ||
throw new Error('Error: toolbarBackround is null.') | ||
throw new Error("Error: toolbarBackround is null."); | ||
} | ||
|
||
// header and title | ||
header.style.position = 'relative' | ||
header.style.position = "relative"; | ||
|
||
if (overlayTitle) { | ||
overlayTitle.style.color = titleColor | ||
overlayTitle.style.position = 'absolute' | ||
overlayTitle.style.width = '100%' | ||
overlayTitle.style.height = '100%' | ||
overlayTitle.style.textAlign = 'center' | ||
overlayTitle.style.color = titleColor; | ||
overlayTitle.style.position = "absolute"; | ||
overlayTitle.style.width = "100%"; | ||
overlayTitle.style.height = "100%"; | ||
overlayTitle.style.textAlign = "center"; | ||
} | ||
|
||
// color overlay | ||
colorOverlay.style.backgroundColor = originalToolbarBgColor | ||
colorOverlay.style.height = `${maximumHeight}px` | ||
colorOverlay.style.position = 'absolute' | ||
colorOverlay.style.top = `${-headerMinHeight * 0}px` | ||
colorOverlay.style.left = '0' | ||
colorOverlay.style.width = '100%' | ||
colorOverlay.style.zIndex = '10' | ||
colorOverlay.style.pointerEvents = 'none' | ||
colorOverlay.style.backgroundColor = originalToolbarBgColor; | ||
colorOverlay.style.height = `${maximumHeight}px`; | ||
colorOverlay.style.position = "absolute"; | ||
colorOverlay.style.top = `${-headerMinHeight * 0}px`; | ||
colorOverlay.style.left = "0"; | ||
colorOverlay.style.width = "100%"; | ||
colorOverlay.style.zIndex = "10"; | ||
colorOverlay.style.pointerEvents = "none"; | ||
|
||
// image overlay | ||
imageOverlay.style.backgroundColor = expandedColor | ||
imageOverlay.style.backgroundImage = `url(${image})` | ||
imageOverlay.style.height = '100%' | ||
imageOverlay.style.width = '100%' | ||
imageOverlay.style.pointerEvents = 'none' | ||
imageOverlay.style.backgroundSize = 'cover' | ||
imageOverlay.style.backgroundPosition = 'center' | ||
imageOverlay.style.backgroundColor = expandedColor; | ||
imageOverlay.style.backgroundImage = `url(${image})`; | ||
imageOverlay.style.height = "100%"; | ||
imageOverlay.style.width = "100%"; | ||
imageOverlay.style.pointerEvents = "none"; | ||
imageOverlay.style.backgroundSize = "cover"; | ||
imageOverlay.style.backgroundPosition = "center"; | ||
|
||
// .toolbar-background | ||
toolbarBackground.style.backgroundColor = originalToolbarBgColor | ||
toolbarBackground.style.backgroundColor = originalToolbarBgColor; | ||
|
||
// .bar-buttons | ||
if (barButtons) { | ||
barButtons.style.pointerEvents = 'all' | ||
barButtons.style.pointerEvents = "all"; | ||
|
||
Array.from(barButtons.children).forEach((btn) => { | ||
// console.log(btn, btn as HTMLElement) | ||
const htmlBtn = btn as HTMLElement | ||
htmlBtn.style.color = titleColor | ||
}) | ||
const htmlBtn = btn as HTMLElement; | ||
htmlBtn.style.color = titleColor; | ||
}); | ||
} | ||
|
||
// .scroll-content | ||
if (scrollContent) { | ||
scrollContent.setAttribute('parallax', '') | ||
scrollContent.style.paddingTop = `${maximumHeight + scrollContentPaddingTop - headerMinHeight}px` | ||
scrollContent.setAttribute("parallax", ""); | ||
scrollContent.style.paddingTop = `250px`; | ||
} | ||
|
||
if (scrollContent) { | ||
scrollContent.addEventListener('scroll', (_e) => { | ||
scrollContent.addEventListener("scroll", (_e) => { | ||
if (!ticking) { | ||
window.requestAnimationFrame(() => { | ||
// to do | ||
|
||
const scrollTop = scrollContent.scrollTop | ||
const scrollTop = scrollContent.scrollTop; | ||
|
||
// Parallax total progress | ||
headerMinHeight = toolbar.offsetHeight | ||
headerMinHeight = toolbar.offsetHeight; | ||
|
||
let progress = (maximumHeight - scrollTop - headerMinHeight) / (maximumHeight - headerMinHeight) | ||
progress = Math.max(progress, 0) | ||
let progress = | ||
(maximumHeight - scrollTop - headerMinHeight) / | ||
(maximumHeight - headerMinHeight); | ||
progress = Math.max(progress, 0); | ||
|
||
let targetHeight = maximumHeight - scrollTop | ||
targetHeight = Math.max(targetHeight, headerMinHeight) | ||
let targetHeight = maximumHeight - scrollTop; | ||
targetHeight = Math.max(targetHeight, headerMinHeight); | ||
|
||
// .toolbar-background: change color | ||
imageOverlay.style.height = `${targetHeight}px` | ||
imageOverlay.style.opacity = `${progress}` | ||
colorOverlay.style.height = `${targetHeight}px` | ||
colorOverlay.style.opacity = targetHeight > headerMinHeight ? '1' : '0' | ||
imageOverlay.style.height = `${targetHeight}px`; | ||
imageOverlay.style.opacity = `${progress}`; | ||
colorOverlay.style.height = `${targetHeight}px`; | ||
colorOverlay.style.opacity = | ||
targetHeight > headerMinHeight ? "1" : "0"; | ||
toolbarBackground.style.backgroundColor = | ||
targetHeight > headerMinHeight ? 'transparent' : originalToolbarBgColor | ||
targetHeight > headerMinHeight | ||
? "transparent" | ||
: originalToolbarBgColor; | ||
|
||
// .bar-buttons | ||
if (barButtons) { | ||
if (targetHeight > headerMinHeight) { | ||
imageOverlay.append(barButtons) | ||
imageOverlay.append(barButtons); | ||
Array.from(barButtons.children).forEach((btn) => { | ||
const htmlBtn = btn as HTMLElement | ||
htmlBtn.style.color = titleColor | ||
}) | ||
const htmlBtn = btn as HTMLElement; | ||
htmlBtn.style.color = titleColor; | ||
}); | ||
} else { | ||
toolbar.append(barButtons) | ||
toolbar.append(barButtons); | ||
Array.from(barButtons.children).forEach((btn) => { | ||
const htmlBtn = btn as HTMLElement | ||
htmlBtn.style.color = 'unset' | ||
}) | ||
const htmlBtn = btn as HTMLElement; | ||
htmlBtn.style.color = "unset"; | ||
}); | ||
} | ||
} | ||
}) | ||
}); | ||
} | ||
setTicking(true) | ||
}) | ||
setTicking(true); | ||
}); | ||
} | ||
} | ||
} | ||
}; | ||
|
||
return { | ||
ref, | ||
} | ||
} | ||
}; | ||
} |