Mobile-like scrolling for desktops - javascript

Mobile touch-screen scrolling works really smoothly out of the box in modern browsers. For example, if you swipe fast across the div, it keeps scrolling for a little while, gradually reducing its' speed, after you release your finger from the screen. This way, you can reach the desired div scroll position a lot more efficiently.
Here's my snippet with a simple div consisting of 50 child items:
const App = () => {
let data = []
for (let i = 0; i < 50; i++) {
data.push({
key: i,
value: `item ${i}`
})
}
const renderItem = (x) => {
return (
'hello' + x.key
)
}
return ( <
div className = "tileView" > {
data.map((x, i) => {
return ( <
div key = {
i
}
className = "item" > {
renderItem(x)
} <
/div>
);
})
} <
/div>
);
};
ReactDOM.render( < App / > , document.getElementById('root'));
.tileView {
display: flex;
overflow-x: scroll;
overflow-y: hidden;
width: 600px;
}
.item {
border: 1px solid gray;
width: 100px;
height: 100px;
flex-shrink: 0;
margin: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I want to find a solution that would make that div scrollable on mouse drag, matching the behavior of touch and drag scrolling.
I have tried implementing this solution. It's working, however, it's not perfect, as the page scroll immediately stops on the mouse-up event.
Ideally, I'm looking for a solution that would implement mobile scrolling behavior on desktops.
Not sure if I'm thinking in the right direction, but maybe it's possible to add an event to a div that would make the browser think that the div is touched rather than clicked?
If not, what would be the best approach to resolve this?

I think what you're looking for is momentum based drag scrolling.
You can see a good example in the codepen provided in the answer to this question. Hopefully that helps :)

Related

Scroll horizontal scroll bar automatically from left to right [duplicate]

I thought this would be fairly easy but I'm stuck.
My code is executing and ignoring the setTimeout.
I am getting the scroll width of my element, then saying while i is less than the width (flavoursScrollWidth), move the horizontal scroll 1px along every second.
That isn't what is happening though, it just executes each pixel movement almost instantly.
I also tried taking the code out of the load event and taking the setTimeout out of the while loop. Then creating a function containing the while loop, and calling the function in a setInterval. Didn't help.
const flavoursContainer = document.getElementById("flavoursContainer")
const flavoursScrollWidth = flavoursContainer.scrollWidth
window.addEventListener("load", () => {
let i = 0
while (i < flavoursScrollWidth) {
setTimeout(flavoursContainer.scrollTo(i, 0), 1000)
console.log(i)
i++;
}
})
.container {
width:300px;
overflow-x:scroll;
white-space: nowrap;
}
<div class="container" id="flavoursContainer">
This is a really long sentence to demo my code, it's just going on and on. Still going. I should have used some default placeholder text but I've started now so I'll keep going.
</div>
I would suggest using setInterval rather than setTimeout and just checking if the container is scrolled to the end. I also found that if you scroll faster, like every 15ms, you get a smoother user experience.
const flavoursContainer = document.getElementById('flavoursContainer');
const flavoursScrollWidth = flavoursContainer.scrollWidth;
window.addEventListener('load', () => {
self.setInterval(() => {
if (flavoursContainer.scrollLeft !== flavoursScrollWidth) {
flavoursContainer.scrollTo(flavoursContainer.scrollLeft + 1, 0);
}
}, 15);
});
.container {
width: 300px;
overflow-x: scroll;
white-space: nowrap;
background-color: #fff;
}
<div class="container" id="flavoursContainer">
This is a really long sentence to demo my code, it's just going on and on. Still going. I should have used some default placeholder text but I've started now so I'll keep going.
</div>

How to check if lowest part of div is visible in viewport?

What is the formula to check if the lowest part of the div is visible in the viewport?
It doesn't matter upper half is visible or gets hidden while scrolling the div
You can use IntersectionObserver to recognise if something is on screen. If you make a placeholder and magnetise it to the bottom of a parent div then you can make it possible.
But if you don't want to use IntersectionObserver API you can try getBoundingClientRect() + window.innerHeight like shown below:
const targetEl = document.querySelector('#target');
const windowsHeight = window.innerHeight;
// I heartily recommend to use some kind of throttling (lo-dash.throttle) here to reduce amount of callback executions
document.addEventListener('scroll', () => {
const bottom = targetEl.getBoundingClientRect().bottom;
if (windowsHeight > bottom) {
console.log('bottom is visible');
} else {
console.log('bottom is hidden');
}
})
/* All css are just for the demo, you only need a JS code */
body {
padding: 300px 20px;
}
#target {
height: 2000px;
width: 100%;
background-color: gray;
}
<div id="target">
content here
</div>

In what line is my inline-block and how high is this line

I have a block with a few inline-blocks inside. These all have varying heights and widths. I have line wrap enabled.
What I'm trying to do is if I have a reference to one of the inline-blocks to
find out what line it is in and
how high that line is. (I can't change the HTML)
2 has the obvious solution of getting the bounding boxes of all blocks in the line and using the highest height as the height of the line. Here I'm just wondering if there is a more performant solution for that.
My main interest lies in a). My idea of an incredibly imperformant solution would be doing b) for all lines in the parent block and then comparing the offset-top of the searched element to the line-heights. I was hoping maybe someone can come up with a better idea?
edit:
relevant part of the html, css and js i'm using:
<div>
<div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div>
</div>
div {
display: block;
background-color: yellow;
height: 600px;
width: 2000px;
}
div > div {
display: inline-block;
padding: 10px;
margin: 5px;
}
document.addEventListener("DOMContentLoaded", function() {
const nodes = document.querySelectorAll("div > div");
for(let i = 0; i < nodes.length; i++) {
nodes[i].style.width = `${5 + Math.random() * 200}px`;
nodes[i].style.height = `${5 + Math.random() * 200}px`;
nodes[i].style.backgroundColor = `hsl(${(Math.random() * 360)}, 100%, 50%)`;
}
const nodeiwanttogetdescribeddataof = nodes[4];
//code to get the data
});
i did not implement the ideas described as i was hoping to not need to. the purpose of the question was to find out whether there is some easy way to do what i need, for example (pseudocode) let theline = node.line.

Pinning Elements with Debounced Scroll Event for Performance

What is the right way to smoothly pin an element according to scroll position?
I tried debouncing a scroll listener for performance but the pinning is not accurate. Even with debouncing set to 10ms it's not smooth and the element doesn't snap cleanly to its initial position.
var scrolling = false;
var stickPosY = 100;
var heights = [];
$(".element").each( function(index) {
heights[index] = $(".element[data-trigger=" + index + "]").offset().top;
});
function pin() {
if ( !$("#aside").hasClass("fixed") ) {
var stickyLeft = $("#aside").offset().left;
var stickyWidth = $("#aside").outerWidth();
var stickyTop = $("#aside").offset().top - stickPosY;
$("#aside").addClass("fixed");
$("#aside").css({"left": stickyLeft, "top": stickyTop, "width": stickyWidth});
}
}
function unpin() {
$("#aside").css({"left": "", "top": "", "width": ""});
$("#aside").removeClass("fixed")
}
$( window ).scroll( function() {
scrolling = true;
});
setInterval( function() {
if ( scrolling ) {
scrolling = false;
var y = window.scrollY;
console.log(y);
// PIN SIDEBAR
y > stickPosY ? pin() : unpin();
//TRIGGERS
for (var i=0; i < heights.length; i++) {
if (y >= heights[i]) {
$('.element[data-trigger="' + i + '"]').addClass("blue");
}
else {
$('.element[data-trigger="' + i + '"]').removeClass("blue");
}
}
}
}, 250 );
Here's my Pen
I tried to use scrollMagic for the project on a scene with a pin and additional triggers but the scrolling wasn't very smooth. So I'm trying to rebuild it with a stripped-down version and debounced listeners. Is this approach possible, or should I rather try to optimize my scrollMagic scene?
As James points out, you can just use position: sticky as one option, but that doesn't work in older browsers and its uses are limited to simpler situations in newer browsers, so I'll continue with the JS solution assuming you want to go that route.
There is a lot going on in your JS, and I think you are probably overcomplicating things, so I will give you a few basics to consider.
When you are toggling things based on scroll, either toggle inline styles or a class, but not both. I would recommend toggling a class because it allows you to have one function that can work on multiple screen sizes (i.e., you can use media queries to change the behavior of your toggled class based on screen size). Also it keeps all your styles in one place instead of having them split between your JS and your stylesheet.
Try to keep the work you're doing while scrolling as minimal as possible. For example, cache references to elements in variables outside your scroll function so you're not continually looking them up every time you scroll a pixel. Avoid loops inside scroll functions.
Using setInterval is not generally the recommended approach for increasing performance on scroll functions. All that is going to do is run a function every X amount of time, all the time, whether you're scrolling or not. What you really want to do is rate-limit your scroll function directly. That way, if you scroll a long ways real fast your function will only be called a fraction of the total times it would otherwise be called, but if you scroll a short distance slowly it will still be called a minimum number of times to keep things looking smooth, and if you don't scroll at all then you're not calling your function at all. Also, you probably want to throttle your function in this case, not debounce it.
Consider using the throttle function from Underscore.js or Lodash.js instead of inventing your own because those ones are highly performant and guaranteed to work across a wide variety of browsers.
Here is a simple example of sticking an element to the top of the screen on scroll, throttled with Lodash. I'm using a 25ms throttle, which is about the maximum amount I'd recommend for keeping things looking smooth where you won't really notice the delay in the element sticking/unsticking as you scroll past your threshold. You could go down to as little as 10ms.
$(function() {
$(window).on('scroll', _.throttle(toggleClass, 25));
const myThing = $('#my-thing');
const threshold = $('#dummy-1').height();
function toggleClass() {
const y = window.scrollY;
if (y > threshold) {
myThing.addClass('stuck')
} else {
myThing.removeClass('stuck');
}
}
});
#dummy-1 {
height: 150px;
background-color: steelblue;
}
#dummy-2 {
height: 150px;
background-color: gold;
}
#my-thing {
width: 300px;
height: 75px;
background-color: firebrick;
position: absolute;
top: 150px;
left: 0;
}
#my-thing.stuck {
position: fixed;
top: 0;
}
body {
margin: 0;
height: 2000px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.0.0/lodash.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="dummy-1"></div>
<div id="dummy-2"></div>
<div id="my-thing"></div>
You could try fixed or sticky CSS positioning:
#element {
position: fixed;
top: 80px;
left: 10px;
}
Position: fixed would keep the element always at 80px from the top and 10px from the left edge regardless of scroll position.
#element{
position: sticky;
top: 0;
right: 0;
left: 0;
}
This is from a project of mine. The element is a nav bar. It sits below a header bar, so when you are at the top of the page, you see the header then the nav below it, and as you scroll down, the header moves off screen but the nav sticks at the top and is always visible.

How to add a touch event (right, left and down at the screen) in javascript?

I'm studying js and making a Tetris game. I already made it in PC dispositives but I want to add some touch events to the game works on mobile dispositives. But I'm stuck at this:
function touchEnd(e) {
if (Math.abs(offset[0]) > Math.abs(offset[1])) {
playerMove(-1);
}
else if (Math.abs(offset[0]) < Math.abs(offset[1])) {
playerDrop();
}
}
(I'm having trouble with this code above)
My idea is when the player touches (not dragging) at the left of the screen (canvas), the playerMove(-1) works, when you touch at the right, the playerMove(1); works, and when you touch at the bottom of the screen, the playerDrop() works. How can I do this? Thank you.
This is an example on how you can split an HTMLElement and send actions based on that
let box;
document.addEventListener('DOMContentLoaded', ()=>{
box = document.querySelector('#test');
box.addEventListener('click', chooseSide);
});
function chooseSide(e){
const {clientX, clientY} = e;
const {clientHeight, clientWidth} = box
if(clientY > (clientHeight/ 2)){
console.log('bottom')
}else if(clientX < (clientWidth/ 2)){
console.log('left')
} else{
console.log('right')
}
}
#test {
box-sizing: border-box;
width: 100vmin;
height: 100vmin;
border: 10px solid #f00;
}
<canvas id="test"></canvas>
You can change the value of the Y comparison, and give more or less space to what the "bottom" is

Categories

Resources