How to keep div always visible in mobile with user scalable page? - javascript

Basically I want to build a page that consists of a weekly calendar. Since the calendar is big I want users to be able to zoom out on it to have an overview and to scroll to the specific hour they want. The problem is that when scrolling the users lose the context of the day they are viewing. Its also important to note that the day is on the left of the page and the scroll is done horizontally.
I built a solution based on window.scrollX that uses a transform to translate the div to the left side of the viewport. This works well in a browser, but unfortunately it doesnt work on mobile. The only way for the translation to be correct is by unzooming the page to maximum on Chrome mobile at least. After trying to debug on Google Chrome responsive design, I see that window.scrollX event holds the value 0 even if the scroll bar is not at the beggining
I would like to know if someone had this problem and have any idea how i should keep a div visible always independently of the zoom and scroll. Note that adding the meta viewport minimum-scale=1 solves the problem but that way I cant unzoom no more.
EDIT:
Here's more info of what I'm doing currently. I'm using react and I have a hook (https://github.com/neo/react-use-scroll-position) that does the following:
useEffect(() => {
let requestRunning = null
function handleScroll () {
if (isBrowser && requestRunning === null) {
requestRunning = window.requestAnimationFrame(() => {
setScrollPosition(getScrollPosition())
requestRunning = null
})
}
}
if (isBrowser) {
window.addEventListener('scroll', handleScroll)
window.addEventListener('touchmove', handleScroll)
return () => {
window.removeEventListener('scroll', handleScroll)
window.removeEventListener('touchmove', handleScroll)
}
}
}, [])
I have changed the getScrollPosition() function to use document.body.getBoundingClientRect() without success, and the original function uses window.scrollX.
Here's a picture of what I want to achieve in mobile:
I get that to work, but by limiting the viewport to disable unzoom. But I want the user to be able to unzoom and scroll horizontally while that div is always on the left side of the visible window. Any ideas/alternatives would be really appreciated!

Related

How to prevent page scrolling when pinch-zooming with Pointer Event?

I've got functionality that allows to zoom images in and out, and it works properly if there's no scrollable parent element.
But when there is a scrollable parent, it doesn't work properly.
The thing is touch-action: none can't be used directly, because it prevents a page from scrolling, but I want to allow users to scroll the page only if one finger is down, and allow to zoom an image if two fingers are down.
It's strange, but the code below wouldn't work:
let fingerCount; // Assume that it has the right value
element.addEventListener("pointerdown", e => {
if (fingerCount === 2) {
// This line will be ignored
element.style.touchAction = "none";
}
});
Is there a way to combine page scrolling and pinch-zooming?
Oh, I was young and silly)) I had to do this:
// This line has to be outside of `pointerdown`
element.style.touchAction = "pan-y";

How to create forced scrolling to anchors on a website on scroll

I have a site where I have each section as 100vh so it fills the height of the screen perfectly. The next step I wanted to implement was disabling the regular scrolling, and on scroll force the screen to jump smoothly to the top of the next 100vh section. Here is the example of this animation / feature:
https://www.quay.com.au/
I was having a hard time finding any answers for this as most things just deal with smooth scrolling when clicking on anchors, not actually forcing div relocation when the user scrolls up / down.
I just wanted to know what code I would need do this...
Thanks, been using stack overflow for a while but first post, let me know if there is anything I can do to make this more clear.
disclaimer: this solution needs some testing and probably a bit of improvements, but works for me
if you don't want to use a plugin and prefer a vanilla JavaScript solution I hacked together a small example how this can be achieved with JS features in the following codepen:
https://codepen.io/lehnerchristian/pen/QYPBbX
but the main part is:
function(e) {
console.log(e);
const delta = e.deltaY;
// check which direction we should scroll
if (delta > 0 && currentlyVisible.nextElementSibling) {
// scroll downwards
currentlyVisible = currentlyVisible.nextElementSibling;
} else if (delta < 0 && currentlyVisible.previousElementSibling) {
// scroll upwards
currentlyVisible = currentlyVisible.previousElementSibling;
} else {
return false;
}
// perform scroll
currentlyVisible.scrollIntoView({ behavior: 'smooth' });
e.preventDefault();
e.stopPropagation();
}
what it does is that it listens for the wheel event and then calls the callback, which intercepts the scroll event. inside the callback the direction is determined and then Element.scrollIntoView() is called to let the browser do the actual scrolling
check https://caniuse.com/#search=scrollintoview for browser support, if you're going for this solution

How Can I Make The Scroll Bar Stay At The Bottom When New Data Is Added

Hello Coders! I am coding a chat system, I would like the scroll bar to stay at the bottom. In this way when someone sends a message the user would not have to scroll down. I would also like it to start at the bottom of the page load. Additionally, the user can scroll up when needed, but when they scroll back down it locks in place. I have tried many times but for some reason it does not work, I was wondering if anyone has a method of doing this?
I've also have struggled with implementing this for a while. The approach I've ended up with relies on using MutationObservable
MutationObservable allows to watch for DOM changes inside element and perform some actions when deeply nested element is changed (in your case, for example, new comment was rendered):
// chatContainer is reference to your chatContainer element
var isLocked = true;
var mutationObserver = new MutationObserver(() => {
if (isLocked) {
scrollToBottom();
}
});
mutationObserver.observe(chatContainer, {
childList: true,
subtree: true,
attributes: false,
});
This gave me a callback, where I likely had to make chatContainer scroll to the bottom.
Scroll to the bottom implementation could be:
function scrollToBottom() {
chatContainer.scrollTop = 99999999999;
}
To change isLocked flag, I had to listen for user scrolls on chatContainer and update it accordingly:
var LOCK_OFFSET = 25; // how many pixels close to bottom consider scroll to be locked
chatContainer.addEventListener('scroll', handleUserScroll);
function handleUserScroll() {
var scrollFromBottom =
chatContainer.scrollHeight -
chatContainer.scrollTop -
chatContainer.clientHeight; // how many pixels user scrolled up from button of the chat container.
isLocked = scrollFromBottom > LOCK_OFFSET; // set new isLocked. lock, if user is close to the bottom, and unlock, if user is far from the bottom.
});
Hope I've explained the general idea. This approach works fine for me. User scroll listening should be improved with events debouncing. And don't forget to dispose scroll event and mutation observer subscriptions.
This is what I've used in my projects. It works on IE, Edge, Firefox, Chrome and Opera. I'm not sure about Safari.
var a = document.querySelector('#divchat');
a.scrollIntoView(false);
I hope it helps.
There are a lot of answers for this question around.
Here is one of them from Automatically scroll down chat div
The main point of that answer is the variables scrollHeight, scrollTop, and clientHeight can be manipulated.
But basically, to scroll down it is to use container.scrollTop = container.scrollHeight;

Is there a reliable way to detect scrolling, on all devices, using javascript/jQuery?

I've been struggling with this for a while, and I'm surprised that doing this isn't more straightforward...
I need to detect when the user scrolls a page, either with the mouse, scrollbar or by touch on mobile devices. jQuery has their scroll() function which works alright, but it requires that the page is actually scrolling. I want to detect the scrolling wether the page is scrolling or not (say I reach the end of the page, and there is nowhere left to scroll too.. I still want to know if the user is trying to scroll)
I found another question that had asked something similar, along the lines of detecting scroll input even when the page isn't scrolling, and I got this chunk of code:
if (document.addEventListener) {
document.addEventListener("mousewheel", MouseWheelHandler(), false);
document.addEventListener("DOMMouseScroll", MouseWheelHandler(), false);
} else {
sq.attachEvent("onmousewheel", MouseWheelHandler());
}
function MouseWheelHandler() {
return function (e) {
var e = window.event || e;
var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
if (delta < 0) {
// increase scroll amount
} else {
// decrease scroll amount
}
}
return false;
}
At first, this seemed to do the trick, but I'm finding it doesn't really return balanced results with different types of mice, and didn't work too smoothly with touch events, which is the core aspect of this question.
I'm using this in a project that does a lot of fancy events on scroll, with the actual page not actually scrolling at all... But I'm running into the problem of it being incredibly slow with all my standard mice, but incredibly fast on my Apple Magic Mouse. I know that there will naturally be some difference here, as the magic mice do scroll quicker, but the difference is far more off balance than it is between the mice normally.
I'm hoping there is a way to improve upon this to get a more reliable result, with all sorts of different inputs. Any suggestions?
Edit:
To clarify, in order for an answer to work for me, it needs to work on an element which is not scrollable. I have a page which does not scroll at all, but which has other events that fire when the user scrolls. This means that I cannot use properties that are based on the window's scroll position (such as scrollTop()).
You should use window.onscroll most usage and then create a new listener to deal specifically with top and bottom scroll conditions I would suggest using a mousewheel event for desktop browsers and a specifically coded touch responder like below to detect if they are trying to scroll, what direction and if that is possible at the current window.scrollY value.
var isOverScroll = function isOverScroll ( touchStartY, touchEndY ) {
if ( Math.abs( touchStartY - touchEndY ) < 5 ) &&
( ( window.scrollY = window.innerHeight && touchStartY - touchEndY > 0 ) ||
( window.scrollY = 0 && touchStartY - touchEndY < 0 ) ) ) {
return false;
}
return true;
}
There is no way to detect scrollbar events, combine this with your current code and only trigger mouse wheel and touch events if the scrollY position is at either 0 or max.
On a side note if you are trying to get rid of the scroll bar completely that is a very bad idea as it is both a wonderful tool for users as well as something that is a standard part of the ui. If you trying to do a scrollable fullpage app and don't want a scroll bar try using slides. Either way don't continue setting the scroll value thats slow instead just move the whole body using css:
transition: transform3d( 0, YOURSCROLLVALUE , 0);
One possible solution is using a plugin for scrolling like
ISCROLL
in this example here :
Example
they use the feature pull to refresh , which will fire upon reaching the maximum scroll available , here by you can use any custom function (even if your item is not scrollable ).

iScroll with native scrolling on one axis

I am using the most wonderful javascript tool iScroll4 http://cubiq.org/iscroll-4 on a mobile website for iOS and Android. Here is what my layout looks like:
The horizontally scroll-able area is making use of iScroll4 with the following settings:
var myScroll = new iScroll('frame', { hScrollbar: false, vScrollbar: false, vScroll: false })
The horizontal scrolling part works great. This issue is what happens when a user attempts to scroll up or down the page placing their finger on the horizontal scrolling area. So I need native vertical scrolling, and iScroll horizontal scrolling on the same area.
What I have tried so far:
Removing e.preventDefault() in the iScroll code (allows for native scrolling, but in BOTH axes).
Removing e.preventDefault() and then disabling horizontal scrolling page wide with this:
var touchMove;
document.ontouchstart = function(e){
touchMove = e.touches[0];
}
document.ontouchmove = function(e){
var theTouch = e.touches[0] || e.changedTouches[0];
var Xer = rs(touchMove.pageX - theTouch.pageX).toPos();
var Yer = rs(touchMove.pageY - theTouch.pageY).toPos();
touchMove = theTouch;
if(Yer > Xer){ e.preventDefault(); }
}
which seems to do nothing. How can I allow for native vertical scrolling in the horizontal scrolling area, without loosing the horizontal scrolling of iScroll? I am really stumped here. Thanks in advance.
(just for the record rs(foo).toPos() is a function that makes foo a positive number regardless of its value).
If you would like to achieve the effect described by Fresheyeball without hacking the core, and without changing from iScroll to swipeview, then iScroll 4 does offer you its event listeners to work with.
myScroll = new iScroll('scrollpanel', {
// other options go here...
vScroll: false,
onBeforeScrollMove: function ( e ) {
if ( this.absDistX > (this.absDistY + 5 ) ) {
// user is scrolling the x axis, so prevent the browsers' native scrolling
e.preventDefault();
} else {
// delegate the scrolling to window object
window.scrollBy( 0, -this.distY );
}
},
});
By doing so, the onBeforeScrollMove-Handler checks whether the scroll direction seems to be horizontal, and then prevents the default handler, thus effectively locking the scroll action to the X-Axis (try commenting it out, you'll see the difference). Otherwise, if the scroll direction needs to be vertical, we make the browser scroll via the window.scrollBy() method. This is not exactly native, but does the job just fine.
Hope that helps
Lukx
[EDIT]
My original solution, which didn't use window.scrollBy() ,did not work on slower Samsung phones, which is why I needed to adapt the answer.
Suggested edit to #Lukx's excellent solution. New versions of iScroll4 place the e.preventDefault() in onBeforeScrollMove which can be overridden. By placing the if block into this option, default is not prevented for vertical scrolling, and vertical can scroll natively.
myScroll = new iScroll('scrollpanel', {
// other options go here...
vScroll: false,
onBeforeScrollStart: function ( e ) {
if ( this.absDistX > (this.absDistY + 5 ) ) {
// user is scrolling the x axis, so prevent the browsers' native scrolling
e.preventDefault();
}
},
});
With iscroll 5, you can set eventPassthrough: true to achieve this. See http://iscrolljs.com/#configuring
OLD ANSWER
UPDATE a special pluggin has been written just to address this problem:
http://cubiq.org/swipeview
I found a way!
add a variable to the top of the document: if android is 15 and is iOS is 3
var scrollTolerance = ( rs().isDevice('android') )?15:3;
disable the original e.preventDefault(); for scrolling. This is under onBeforeScrollStart:
the in _move just under
timestamp = e.timeStamp || Date.now();
add this line
if( Math.sqrt(deltaX*deltaX) > scrollTolerance){e.preventDefault();}
What this does is the following:
the scrollTolerance sets, you guessed it, a tolerance for finger direction. We don't want to demand a perfect vertical angle to get the up down native scroll. Also iOS does not detect properly and will never be higher than 4 for some reason so I used 3. Then we disable iScroll's standard e.preventDefault(); which prevents native vertical scrolling on our bi-scrollable area. Then we insert e.preventDefault(); only upon move and based on finger direction from tolerance.
This does not work perfect. But is acceptable and works on iOS and Android. If anyone sees better ways please post here. This is something I (and assume others) need to use regularly, and we should have a perfect rock solid solution.
Thanks.
Please test this solution from Adam.
https://gist.github.com/hotmeteor/2231984
I think the trick is to add the check in onBeforeScrollMove. First get the initial touch position in onBeforeScrollTouchStart and then in onBeforeScrollMove check the new position and then disable the required scroll based on the difference.
iScroll 5 supports native scrolling of any axis!
http://iscrolljs.com/
on iScroll5 just set eventPassthrougt to true. That fixes it.

Categories