How to make an element fixed on top of the mobile keyboard? - javascript

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.
if resize happens, that means either the virtual keyboard is up/down or the browser header/footer is shown/disappeared by the scroll.
debounce the resize event so to store the visualViewport.height before the resize and after the resize.
when resize event ends, compare the the visualViewport.height before and after and set the difference as the style.bottom of the toolbar.
if 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)

Related

How to obtain the left, right, top and bottom position of scroll in a div in HTML with Javascript

I would like to draw a <div> box with fixed height, width and scrollbars and register the location of the scrolled view. I have found the function scrollTop but don't know how to get the bottom scroll location.
Which CSS do I need to draw the box with scrollbars?
How can I access the left, right, top and bottom position inside (relative to) the box with Javascript?
I would use these 4 numbers to draw a <div> inside.
Starting with your second question:
scrollTop is the vertical position (in px) of the scrollbar, relative to the top position. So if your scrollbar is at the very top, scrollTop = 0.
It's the same for scrollLeft, but horizontal.
You can set these values via js to bring the scrollbar to a certain position programmatically.
So, if you want to know when the scrollbar is on top, you simply register an event listener for scrolling and wait for scrollTop = 0.
To check if the scrollbar is at the very bottom, you need a bit of calculation since you need to know the scroll height of the container (scroll height - scrollTop = 0).
See the following code (using jquery but it also works with plain js) which fires an event when the scrollbar reaches start or end position (this is for horizontal scrolling, so you have to replace "left" with "top" and "width" with "height"):
$('#yourDivId').on('scroll', function () {
if (scrollbarIsAtStart($(this))) {
$(document).trigger("scrollbar.left");
}
if (scrollbarIsAtEnd(this, $(this))) {
$(document).trigger("scrollbar.right");
}
})
const scrollbarIsAtStart = (jQuery) => {
if (jQuery.scrollLeft() == 0) {
return true;
}
return false;
}
const scrollbarIsAtEnd = (e, jquery) => {
//Minus one, probably rounding issue!?
if (jquery.width() + jquery.scrollLeft() >= e.scrollWidth - 1) {
return true;
}
return false;
}
So there are two events triggered, one for the scrollbar reaching start position, one for the end of the container. Now, you can listen to these events and handle them:
$(document)
.on('scrollbar.left', {}, function (event) {
//do whatever you want to do
})
Regarding your first question, I'm not a css expert, but if I look at bootstraps table-responsive class (which enables horizental scrolling), it looks like this:
.table-responsive {
display: block;
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
Vertical scrolling is enabled by default if you define a fixed height on your div and the data exceeds this size. You can of course override the width with a fixed value or a different percentage.

scrollBy does not work while user is scrolling (iOS only)

I have a list of items that has an item prepended to it at a regular interval (think a feed of data). The list should update in real-time as new items come in, however if the user scrolls down it should "lock" in place to make the list easier to read. (If a user has scrolled, they want to be able to read the items without them being pushed down every time a new item is added)
It appears that on Chrome (desktop) and Android, the browser implements this automatically. However, on iOS it does not.
As a work around, I figured I could use scrollBy to move the scroll position when a new item is added IF the user has scrolled down. This mostly works, and prevents the list from jumping.
However, if the user is currently in the process of scrolling, scrollBy seems to have no effect and the items jump around.
To reproduce:
Open on iOS: https://codepen.io/Cyral/full/JjbbPaj
Observe that new items are added every second. Scroll down the list a bit and notice how items are no longer moving (great!), however try scrolling slowly (without taking your finger off the screen) and notice how the items start updating again.
It appears that whatever keeps track of the user scroll amount overrides the scrollBy command, resulting in the list jumping around. If you scroll a bit and take your finger off and let it "smooth scroll" you can also see how it jumps a bit. Is there a way to create the desired behavior of the list "pausing" when scrolled away, but updating in realtime once scrolled to the top?
Here is a reference to see the desired behavior on desktop: (It is the same code, except the scrollBy call has been removed as it is not needed) https://codepen.io/Cyral/full/PobboYE
Update: It appears it is only broken on iOS 14. On 12 and 13 it works as it does on desktop, with no hacky scrollBy workaround.
Like this?
const container = document.getElementById("container");
let textNum = 0;
setInterval(() => {
container.innerHTML = `<p>some text ${++textNum}</p><br/>` + container.innerHTML;
//scroll
savedScroll && (container.scrollTop = container.scrollHeight - savedScroll);
}, 700);
let savedScroll = false;
container.addEventListener("scroll", () => {
if (container.scrollTop <= 10 /*if scroll is near top of container*/) {
savedScroll = false;
} else {
savedScroll = container.scrollHeight - container.scrollTop;
}
});
body {
line-height: 5px;
margin: 0px;
}
div {
width: 200px;
height: 190px;
overflow-y: scroll;
border: 1px solid #000000;
}
<div id="container"></div>
It listens for scroll, and if the current scroll is at the top, it disable auto scrolling, else, save current scroll relative to bottom of container. The scroll is set to containerHeight - savedScroll everytime the container is updated.

wheel Event Reliability

I'm working on a web project that has animations and page changes on the scroll ( specifically, scroll direction ) and I've been looking for multiple possible good and reliable solutions.
I've been detecting the scroll direction by detected the window's scrollY with the user's previously saved scrollY that I have saved in a variable. The only problem is that the scroll event doesn't fire when at the top or the bottom of page, even though the content is all absolute/fixed positioned.
I want to turn to the wheel event because of its deltaY values from the event, and it still fires when at the top of bottom of the page so I can remove the scrollbar and keep the body of the page 100vh.
The Mozilla dev docs say:
Don't confuse the wheel event with the scroll event. The default
action of a wheel event is implementation-specific, and doesn't
necessarily dispatch a scroll event. Even when it does, the delta*
values in the wheel event don't necessarily reflect the content's
scrolling direction. Therefore, do not rely on the wheel event's
delta* properties to get the scrolling direction. Instead, detect
value changes of scrollLeft and scrollTop of the target in the scroll
event.
(https://developer.mozilla.org/en-US/docs/Web/API/Element/wheel_event)
And I'm also curious if the wheel event will work correctly on mobile with touch?
Here's a good example of what I'm trying to replicate: https://reed.be
There is no scrollbar, yet things still happen based on your scrolling.
CanIuse shows full compatibility of the wheel event with modern browsers, and some older versions.
see here -> https://caniuse.com/#feat=mdn-api_wheelevent
I've found a solution that references the wheel event (How to determine scroll direction without actually scrolling), though my question still applies -
How reliable is the wheel event across devices and browsers, including mobile?
I am limited to my own current version browsers and android devices for testing.
You can fool the browser by setting the additional height on the body to match the content width and setting the overflow to scroll. Then use some basic script, to set the scrollLeft property of your container to equal the window scrollY.
You will need to set the height of the body equal to the total width of the panels.
body {
height: 400vh; // 4 panels of 100vw each
...
}
.panel {
width: 100vw;
...
}
JS
const viewPort = document.querySelector('#viewport');
let lastScroll = 0;
window.addEventListener('scroll', (e) => {
let scrollY = window.scrollY;
// scroll the container by and equal amount of your window scroll
viewPort.scrollLeft = scrollY;
lastScroll = scrollY;
});
Rough JSFiddle Demo

Which event to listen to during inertia/momentum scroll on Cordova iOS w/ React

I have a React app running on iOS through cordova/phonegap.
In one component, I have a toolbar that sticks to the top of the screen once you scroll past it:
<div id="content">Some stuff in here</div>
<div id="tool-bar">
<div class="tool">Tool 1</div>
<div class="tool">Tool 2</div>
</div>
<div id="some-stuff-below-toolbar">Stuff</div>
You get the picture.
Whenever the user scrolls, the Y position is calculated, and if it's below the offset, the tool bar gets a class 'sticky' and CSS to fix it to the top:
ReactJS:
componentDidMount() {
document.addEventListener('scroll', this.updateScrollY);
document.addEventListener('touchmove', this.updateScrollY);
}
updateScrollY() {
const current_y = window.pageYOffset || document.documentElement.scrollTop;
this.setState({...this.state, current_y});
}
render() {
let classNames = '';
if(this.state.current_y >= this.state.offset) {
classNames = 'sticky';
}
return(...some JSX...
<div id='tool-bar' className={classNames}> ... etc more JSX);
}
CSS:
.sticky {
position: fixed;
top: 0px;
left: 0px;
}
Now, this all works in Chrome/Safari, but on iOS/Cordova, the scroll event is not fired during momentum scrolling. So, if a user scrolls past the offset limit whilst holding the screen, fine, because the touchmove event keeps firing. However, if the user scrolls for a bit, releases the screen, and the momentum / inertia scrolling makes the view scroll past the offset, nothing happens until the scroll ends (because then the scroll event is fired).
Question:
Is there a separate event I can listen for, or is there a Cordova
setting that makes javascript execute during inertia/momentum
scrolling?
setInterval()
I've also tried setting an interval on touchend (the user stopped scrolling/touching) and removing it later, but it seems that during momentum/inertia scrolling, no javascript is ran at all: the setInterval doesn't execute during that time.

Programmatically halt -webkit-overflow-scrolling

I have a phonegap application that uses iOS native scrolling through -webkit-overflow-scrolling in a div. I want to be able to manually halt an ongoing scroll when the user clicks a button (to scroll back to the top of the page). Is this doable?
This is actually very possible when using fastclick.js. The lib removes the 300ms click delay on mobile devices and enables event capturing during inertia/momentum scrolling.
After including fastclick and attaching it to the body element, my code to stop scrolling and go to the top looks like this:
scrollElement.style.overflow = 'hidden';
scrollElement.scrollTop = 0;
setTimeout(function() {
scrollElement.style.overflow = '';
}, 10);
The trick is to set overflow: hidden, which stops the inertia/momentum scrolling. Please see my fiddle for a full implementation of stop scrolling during inertia/momentum.
Unfortunately this is not possible at the moment. The scroll event is triggered only when the scrolling has come to an end. As long as the momentum keeps moving the content no events are fired at all. You can see this in Figure 6-1 The panning gesture in Apple's "Safari Web Content Guide".
I also created a fiddle to demonstrate this behavior. The scrollTop value is set after iOS is done animating.
You can capture a touch event using 'touchstart' instead of 'click', as the click event sometimes doesn't seem to get fired until the momentum scroll completes. Try this jQuery solution:
$('#yourTrigger').on('touchstart', function () {
var $div = $('.yourScrollableDiv');
if ($div.scrollTop() === 0) {
return false; //if no scroll needed, do nothing.
}
$div.addClass('scrolling'); //apply the overflow:hidden style with a class
$div.animate({
scrollTop: 0
}, 600, function () {
$div.removeClass('scrolling'); //scrolling has finished, remove overflow:hidden
});
}
where the 'scrolling' class simply has the CSS property, overflow:hidden, which as #Patrick-Rudolph said, will halt any momentum scrolling in progress.
.scrolling {
overflow: hidden;
}
Note: It's best to use a callback function to tell when your scroll animation finishes, rather than setting a timer function.

Categories