I am building an editor using editorJS. For PC, I created a sticky toolbar at the top which follows the viewport scroll. However, for mobile, the stikcy toolbar just won't get fixed on top of the viewport because of the damn virtual keyboard.
So I decided to remove the position: sticky;
and apply position: fixed; bottom: 0;
for mobile screen so that it sticks on top of the keyboard. I thought I would be able to calculate the right position for the toolbar in the resize
and scroll
event handlers. The procedures are as follows.
resize
happens, that means either the virtual keyboard is up/down or the browser header/footer is shown/disappeared by the scroll.resize
event so to store the visualViewport.height
before the resize and after the resize
.resize
event ends, compare the the visualViewport.height
before and after and set the difference as the style.bottom
of the toolbar.scroll
happens, visibility: hidden;
the toolbar and calculate the position when the event ends. (also debouncing the scroll
event)visiblity: visible
the toolbar to show it on top of the keyboard.But, I was not able to achieve it because the [2] did not work. The resize event did not catch the visualViewport.height
before the resize even with the debounce.
handleIOSKeyboardAppear(event) {
if (!this.isMobileResizing) {
console.log('start!', window.visualViewport?.height) <-- the same value
this.isMobileResizing = true
}
console.log('resizing...')
if (this.mobileResizeDebounceTimerId) {
clearTimeout(this.mobileResizeDebounceTimerId)
}
this.mobileResizeDebounceTimerId = setTimeout(() => {
console.log('end!', window.visualViewport?.height) <-- the same value
this.isMobileResizing = false
}, 500)
I found my own solution to this. Basically re-calculating the style.bottom every time the resize, scroll event happens.
the value of the style.bottom: window.innerHeight - window.visualViewport.offsetTop - window.visualViewport.height
// MARK: hang events on creation.
created() {
if (this.isMobile) {
window.visualViewport?.addEventListener('resize', this.handleIOSKeyboardAppear)
window.addEventListener('scroll', this.handleMobileScroll)
}
}
...
handleMobileScroll(event) {
// MARK: hide the toolbar when scroll starts.
if (!this.isMobileScrolling) {
this.$refs.blockToolbar.style.visibility = 'hidden'
this.isMobileScrolling = true
}
if (this.mobileScrollDebounceTimerId !== null) {
clearTimeout(this.mobileScrollDebounceTimerId)
}
// MARK: if scroll ends, show the toolbar after 150ms.
this.mobileScrollDebounceTimerId = setTimeout(() => {
const toolbarBottomPosition =
window.innerHeight -
window.visualViewport?.offsetTop -
window.visualViewport?.height
const blockToolbar = this.$refs.blockToolbar
blockToolbar.style.visibility = 'visible'
blockToolbar.style.bottom = `${toolbarBottomPosition}px`
this.isMobileScrolling = false
}, 150)
},
handleIOSKeyboardAppear(event) {
// MARK: hide the toolbar when mobile VisualViewport resize starts.
if (!this.isMobileResizing) {
this.$refs.blockToolbar.style.visibility = 'hidden'
this.isMobileResizing = true
}
if (this.mobileResizeDebounceTimerId !== null) {
clearTimeout(this.mobileResizeDebounceTimerId)
}
// MARK: if mobile VisualViewport resize ends, show the toolbar.
this.mobileResizeDebounceTimerId = setTimeout(() => {
const toolbarBottomPosition =
window.innerHeight -
window.visualViewport?.offsetTop -
window.visualViewport?.height
this.$refs.blockToolbar.style.bottom = `${toolbarBottomPosition}px`
this.$refs.blockToolbar.style.visibility = 'visible'
this.isMobileResizing = false
}, 150)
},