OnScroll Fold the View - React Native - javascript

I'm really bad at math, I am trying to calculate the top section of the scrollView as I want it to come on top of the top View to make more place for the scrollView.
I know that I could use Animate.View to accomplish this, But due to a component(RecyclerListView) Im unable to do that. So my Idee is while I scroll Down I move the component ItemList up until is at -150 and when I scroll up, I will move until the value scrollY hit 0.
There is already a post here on Stack(here) That displays what I want.
Here is my code.
<View> this is the View i want to cover/fold <View>
<ItemList style={{top:scrollY}}
onScroll={(nativeEvent, offsetX, offsetY)=>{
// when scroll down, scrollY should not exeede 0
// when i scroll up, scrollY should not be more -150
var maxScroll = 0;
var minScroll = -150;
// How should i calculate and set setScrollY()
}}
columnPerRaw={columnPerRar}
itemHeight={150}
onIni={(s: any) => {
setScroll(s);
}}
onItemPress={(item, index) => itemClick(item as item)}
items={data ?? []}
renderItem={renderItem}
onEndReached={() => {
if (globalContext.value.panination && !isLoading)
setEffectTrigger(Math.random());
}}
keyName="name"
/>
Update
I have done like #Nnay said but its not to smoth
var maxScroll = 0; // when i scroll up, scrollY should not be more then 0
var minScroll = -150; // when scroll down, scrollY should not exeede -150
var top = lastPos - offsetY;
if (top >minScroll && top <=maxScroll)
setScrollY(top);
lastPos = offsetY;
I have done it like

no matter how you choose to trigger this behaviour (be it scroll position or scroll direction), you should probably use conditional classes.
to get the scroll direction, you will need to store the previous scroll position.
if oldPosition - newPosition < 0 you have scrolled down, else you have scrolled up.
if you want the position of the element to trigger it, you should opt for setting a boolean to true or false which triggers the class. something like:
<View class={{this.show ? 'show' : ''}}>
where show is a boolean local to your component.
your show class can then handle the height of the element which will automatically push the following content down (provided it isn't positioned absolute) and you can smoothen this by using transition: .25s in css. otherwise it will jump
your scroll handler could then set the boolean depending on the scroll direction with something like:
this.show = oldPos - newPos > 0;
which will return true if the user has scrolled up which seems to be the behaviour you referenced in the link
let me know if this satisfies your question so I can edit and adapt the answer accordingly
ps: if you just set the top of your element while it has a static position, it won't work. also it will just jump to that position since you're not smoothing anything
EDIT: if CSS animations and transitions don't work and you cannot use react-native's Animate.View, the only option that doesn't require external packages is to set an interval animation if react-native doesn't support the AnimationsAPI
I haven't tried using the AnimationsAPI in react-native yet so I would advise to test that first since it's much more performant.
if you want to use the interval animation, I would still recommend sticking to the boolean solution above, but removing the class and adding a height variable in the style attribute of the <View>
make sure to store your interval in a variable so you can clear it.
if (this.height < 150 /* no idea what height you need, play around */ && show && !this.interval){
this.interval = setInterval(() => {
this.height += 1;
}, 10)
} else if (this.height > 0 && !show && !this.interval) {
this.interval = setInterval(() => {
this.height -= 1;
}, 10)
} else {
clearInterval(this.interval)
}
something like this should work. keep in mind that I haven't used interval animations in years so you might need to play around with some values

Related

JavaScript: Infinite scroll near bottom in div (70% near bottom)

I want to implement an infinite scroll inside a div whereby data is fetched when a user scrolls past 70% of the content.
Here is my current code:
element.onscroll = () => {
let dom = document.getElementById('scrollable');
let scrollY = dom.scrollHeight - dom.scrollTop;
let height = dom.offsetHeight;
let offset = height - scrollY;
if (offset == 0 || offset == 1) {
getMoreData()
}
};
Is there a way I can check whether the scroll is over 70%? Without executing getMoreData() multiple time?
I need to make sure that getMoreData function runs only once when scrolling down.
Implementations in Vue are also welcome.

Why this hide menu / navigation on scroll works?

I've been looking on the internet for vanilla Javascript implementation of hiding menu when scrolled—and found this solution:
var previousScroll = window.pageYOffset;
window.addEventListener( 'scroll', hideMenu );
function hideMenu() {
var menubar = document.querySelector('.navigation');
var currentScroll = window.pageYOffset;
if ( currentScroll > previousScroll) {
menubar.style.transform = "translateY(-60px)";
}
else {
menubar.style.transform = "";
}
previousScroll = currentScroll;
}
What I don't understand is, why this works? Why the variable "previousScroll" value could be different with "currentScroll" value?
I've tried to put previousScroll inside the function hideMenu, and it doesn't work.
Your function hideMenu()is called after the execution of the scroll.
So, windows.pageYOffsetwill return the Y position after scrolling, while the previousScrollis the Y before scrolling.
That's why it whorks ;)
Here how does it work step by step :
Step 1 (page loaded)
previousScroll => 0 (top of the page)
pageYOffset=> 0 (instant Y
window scroll position)
currentScroll => ??
Step 2 (scroll)
previousScroll => 0 (no changes)
page is doing scroll ('scroll' event is fired)
pageYOffset => 100 (instant Y window scroll position)
'scroll' callback function is called (hideMenu() here)
update of currentPosition (= pageYOffset = 100)
update of previousPosition (now is equals to currentPosition)
....
Have a look to this jsFiddle
The hideMenu function is called when the user scrolls. Therefore, we have access to the current pageYOffset in the page.
We have no way to know where we were before the scroll. That is, unless we kept a reference to it before the 'scroll' event happened.
If you put your variable in the function, previous and current will return the same value and we won't be able to compare them.
currentScroll is updated when the user scrolls. It represents the current scroll offset.
If the user scrolls down (currentScroll > previousScroll) then we decide to hide the menu bar. Otherwise we reset its position.
At the end, we update previousScroll to keep the last scroll position for the next hideMenu function call (steps 1 to 3)

scroll to a div when scrolling, and scroll to top when div disappear

I have 2 divs on my webpage. first div is "#pattern" (red one), and second on is "#projets".(blue one)
when use scrolls for the first time, the window scrolls automaticaly to the the second div "#projets". I'm using jquery scroll-To plugin.
it works nice, even if when the users scroll with a large amount of scroll there could be on offset from the "#projets" div... If someone has an idea to correct this would be nice, but that's not my main trouble...
Now i'm trying to scroll back to the top of the page ("#pattern" div) as soon as "#pattern" div reappears when scrolling, the red one. so basically it should be as soon as the offset from the top of my screen of my div "#projets" is supperior to 1.
I've tried so many solutions without results, using flags, multiple conditions... it can be the same kind of thing as on this page, but user should be abble to scroll freely inside the page, not scrolling from hash to hash :
http://www.thepetedesign.com/demos/onepage_scroll_demo.html
here is my html :
<div id="pattern"></div>
<div id="projets"></div>
my css :
#pattern {
height:300px;
width: 100%;
background-color:red
}
#projets {
height:800px;
width: 100%;
background-color:blue
}
and my jquery :
var flag=0 ;
$(window).on('scroll',function(){
var top_projets_position = $("#projets").offset().top - $(window).scrollTop();
if((flag==0) && $(window).scrollTop()>1){
$(window).scrollTo('#projets', 500);
flag=1;
}
if($(window).scrollTop()==0){
flag=0;
}
});
here is jsfiddle :
http://jsfiddle.net/jdf9q0sv/
hope someone can help me with this, I can't figure out what I'm doing wrong, maybe a wrong method ! thanks
It looks like you need to track 3 things:
The scroll direction occurs.
The area you are currently viewing.
If scroll animation is currently happening (we need to wait until it's done, or problems will occur).
http://jsfiddle.net/vx69t5Lt/
var prev_scroll = 0; // <-- to determine direction of scrolling
var current_view ="#pattern"; // <-- to determine what element we are viewing
var allowed = true; // <-- to prevent scrolling confusion during animation
var top_projets_position = $("#projets").offset().top + 1;
$(window).on('scroll',function(){
var current_scroll = $(window).scrollTop();
if(current_scroll < top_projets_position && current_view=="#projets" && current_scroll < prev_scroll){
scrollToTarget("#pattern");
}
if($(window).height() + current_scroll > top_projets_position && current_view=="#pattern" && current_scroll > prev_scroll){
scrollToTarget("#projets");
}
prev_scroll = current_scroll;
});
function scrollToTarget(selector){
if(allowed){
allowed = false;
$(window).scrollTo(selector, {
'duration':500,
'onAfter': function(){ allowed = true; current_view = selector;}
});
}
}
This is just a quick solution based on your original code. A better solution would be to do something more Object Oriented (OOP) and track values in an object. Perhaps take an array of elements on object creation, grab all the boundaries and use the boundaries in your scroll handler to determine when to scroll to the next div.

Infinite Scrolling of <table /> element in react.js

I have a particularly interesting problem with respect to infinite scrolling using react.js.
The goal here is to make sure no matter how large a table becomes, we only ever let render() return a fixed subset of all rows.
We will let lowerVisualBound and upperVisualBound denote the subset of rows to render() onto the DOM.
Note these bounds are set to be larger than the viewport, causing a scrollbar to appear.
We will modify lowerVisualBound and upperVisualBound as the user scrolls.
Here, we further denote
height as the height of the visible portion of the table
totalHeight as the height of the entire table (that includes all rows between lowerVisualBound and upperVisualBound
scrollTop and state.lastScrollTop as the current and previous scroll top respectively
The Following Snippet Kind of Does the Trick - Except the scrollbar itself does not change position after additional data has been loaded (i.e. upper or lower VisualBound reset). This causes the user's view of the data to jump.
const rowDisplayBoundry = 2 * this.props.pageSize;
if (scrollTop < this.state.lastScrollTop && scrollTop <= 0.0 * totalHeight) {
// up scroll limit triggered
newState.lowerVisualBound = Math.max(this.state.lowerVisualBound - this.props.pageSize, 0);
newState.upperVisualBound = newState.lowerVisualBound + rowDisplayBoundry;
} else if (scrollTop > this.state.lastScrollTop && (scrollTop + height) > totalHeight) {
// down scroll limit triggered
newState.upperVisualBound = this.state.upperVisualBound + this.props.pageSize;
newState.lowerVisualBound = newState.upperVisualBound - rowDisplayBoundry;
}
// TODO now what do we set scrollTop to, post these mutations? (presumably using a setTimeout())
Can anyone suggest an algorithm to compute a new scrollTop such that changing the visual bound preserve the user's view?
Note I believe this should be theoretically possible because the # of rows between upper & lower visual bound is set to be > what can be displayed in the viewport. Therefore, after each mutation in those bounds, the user does not lose any rows that he was viewing immediately before the mutation. It is only a matter of computing the correct location the scrollbar post-mutation.
The following appears to have worked ... though not sure if there are corner cases where it doesn't (please excuse the rather liberal use of jQuery selector for this demostration)
handleScroll: function (e) {
const $target = $(e.target);
const scrollTop = $target.scrollTop();
const height = $target.height();
const totalHeight = $target.find("tbody").height();
const avgRowHeight = totalHeight / (this.state.upperVisualBound - this.state.lowerVisualBound);
/**
* always update lastScrollTop on scroll event - it helps us determine
* whether the next scroll event is up or down
*/
var newState = {lastScrollTop: scrollTop};
/**
* we determine the correct display boundaries by keeping the distance between lower and upper visual bound
* to some constant multiple of pageSize
*/
const rowDisplayBoundry = 2 * this.props.pageSize;
if (scrollTop < this.state.lastScrollTop && scrollTop <= 0) {
// up scroll limit triggered
newState.lowerVisualBound = Math.max(this.state.lowerVisualBound - this.props.pageSize, 0);
newState.upperVisualBound = newState.lowerVisualBound + rowDisplayBoundry;
// if top most rows reached, do nothing, otherwise reset scrollTop to preserve current view
if (!(newState.lowerVisualBound === 0))
setTimeout(function () {
$target.scrollTop(Math.max(scrollTop + this.props.pageSize * avgRowHeight, 0));
}.bind(this));
} else if (scrollTop > this.state.lastScrollTop && (scrollTop + height) >= totalHeight) {
// down scroll limit triggered
newState.upperVisualBound = this.state.upperVisualBound + this.props.pageSize;
newState.lowerVisualBound = newState.upperVisualBound - rowDisplayBoundry;
setTimeout(function () {
// TODO ensure that new scrollTop doesn't trigger another load event
// TODO ensure this computationally NOT through flagging variables
$target.scrollTop(scrollTop - this.props.pageSize * avgRowHeight);
}.bind(this));
}
this.setState(newState);
}

Image Rotation using pure Javascript

PLEASE DO NOT RECOMMEND JQUERY - I AM DOING THIS EXERCISE FOR LEARNING PURPOSES.
I have implemented a JavaScript, which rotates images (_elementSlideChange) on a timer, using a set interval of 10 seconds. Also I have added a slide functionality to this, which is 7 milliseconds (_slideImage).
The image rotates automatically every 10 seconds on page load, and I have also provided next and previous buttons, which allow the user to change the images manually.
_elementSlideChange: function () {
var myString;
var myText;
for (var i = 0; i < this._imgArray.length; i++) {
var imageArr = "url(" + this._imgArray[i].src + ")";
var imageBg = this._imageHolder.style.background + "";
if (imageArr == imageBg) {
if (i == (this._imgArray.length - 1)) {
myString = "url(" + this._imgArray[0].src + ")";
myText = this._infoArray[0];
} else {
myString = "url(" + this._imgArray[(i + 1)].src + ")";
myText = this._infoArray[i + 1];
}
}
}
this._imageNextSlide.style.background = myString;
this._imageNextSlide.style.background);
this._infoElement.innerHTML = myText;
this._myTimer = setInterval(MyProject.Utils.createDelegate(this._slideImage, this), 7);
},
_slideImage: function () {
if (parseInt(this._imageHolder.style.width) >= 0 && parseInt(this._imageNextSlide.style.width) <= 450) {
this._imageHolder.style.backgroundPosition = "right";
this._imageHolder.style.width = (parseInt(this._imageHolder.style.width) - 1) + 'px';
console.log(this._imageNextSlide.style.background);
this._imageNextSlide.style.width = (parseInt(this._imageNextSlide.style.width) + 1) + 'px';
} else {
console.log("reached 0px");
if (parseInt(this._imageHolder.style.width) == 0) {
this._imageHolder.style.background = this._imageNextSlide.style.background;
this._imageHolder.style.width = 450 + 'px';
this._imageHolder === this._imageNextSlide;
this._imageHolder.className = "orginalImage";
this._imageNextSlide.style.width = 0 + "px";
this._imageNextSlide = this._dummyImageNextSlide;
this._imagesElement.appendChild(this._imageHolder);
this._imagesElement.appendChild(this._imageNextSlide);
clearInterval(this._myTimer);
}
clearInterval(this._myTimer);
clearInterval(this._elementSlideChange);
}
}
So when the user clicks on the Next arrow button, the event listener for "click" is triggered. This creates a div for the current image on display, and creates a new div, which will contain the next image. The image slide and rotation works correctly (whether it's onLoad or onClick). The issue I have is if I click the Next button, while the new div image is sliding into position, it causes it to run into an infinite loop, so the same div with the image to be displayed keeps sliding in, and the more you click the Next button, the faster the image starts to rotate.
I have tried putting a clear interval for the image rotation and slider, but I do understand my code is wrong, which causes the infinite loop of the sliding image. And I know I am close to finishing the functionality.
Can anyone please advise where I could be going wrong? Or should I try to implement the sliding DIV in another way?
Once again please don't recommend jQuery.
And thank you for your help in advance.
Kush
To solve the issue, I did re-write the entire code, where I had a next and previous button event listener.
myProject.Utils.addHandler(this._nextImageElement, "click", myProject.Utils.createDelegate(this._changeImage, this));
Both the buttons will call the same function :
_changeImage: function (e)
In this function I check to see if the function is Transition (changing images),
I declare a boolean var forward = e.target == this._nextImageElement;
Then check to see the current index if forward ? Add 1 else minus 1
this._currentImageIndex += forward ? 1 : -1;
If its at the end of the Array and forward is true, assign the this._currentImageIndex to reset to 0 or Array.length – 1 if it’s in reverse
Then call another function which gives the ‘div’ a sliding effect. In this case call it this._transitionImage(forward);
In this function, set the this._inTranstion to true. (Because the div’s are sliding in this case).
The following code solved the issue i was having.
this._slideImageElement.style.backgroundImage = "url(\"" + this._imgArray[this._currentImageIndex].src + "\")";
this._slideImageElement.style.backgroundPosition = forward ? "left" : "right";
this._slideImageElement.style.left = forward ? "auto" : "0px";
this._slideImageElement.style.right = forward ? "0px" : "auto";
The above code is very important as the object is to place the “sliding in div” Left or Right of the current Visible “div” to the user, and this is mainly dependent on if the forward variable is true or false.
var i = 0;
Then start the transition by
setInterval( function() {
this._currentImageElement.style.backgroundPosition = (forward ? -1 : 1) * (i + 1) + "px";
this._slideImageElement.style.width = (i + 1) + "px";
Notice the forward will determine if the bgPosition will go to the left if its forward as we multiple by -1 or +1,
So for example
If the user clicks NEXT BUTTON,
Forward = true
So the first thing we do is set the
this._slideImageElement.style.backgroundPosition = "left"
Then
this._slideImageElement.style.left = "auto"
this._slideImageElement.style.right = "0px"
This means when the sliding image moves in its background position is LEFT but the div is placed on the RIGHT to 0px;
then this._currentImageElement.style.backgroundPosition = -1 * (i + 1)
Which moves the position of the currentImageElement to the left by 1px,
Increase the width of the slideImage which in this case is right of the current div,
and as the current div moves to the left the sliding image starts to appear from the right. (By default set the width of slideImageElement to 0px so the div exists but isn’t visible to the user). This gives it the slide effect of moving forward new image coming from the right.
this._slideImageElement.style.width = (i + 1) + "px";
then declare it to stop when it it’s the image width. In this case it will be 500px.
if ((i = i + 2) == 500) {
In this if statement reset the currentImageElement background and the background position “right” or “left” don’t really matter as long it has been reset.
Clear the interval
Set the transition to false again
Then call a setTimeout for the function changeImage, which will continue until the slide is completed.
The following shows the reset code as this is very important to prevent repeating the same image (This solved my entire issue)
// set the current image to the "new" current image and reset it's background position
this._currentImageElement.style.backgroundImage = "url(\"" + this._imgArray[this._currentImageIndex].src + "\")";
this._currentImageElement.style.backgroundPosition = "right";
// reset the slide image width
this._slideImageElement.style.width = "0px";
// clear the transition interval and mark as not in transition
clearInterval(this._transitionInterval);
this._inTransition = false;
// setup the next image timer
this._nextImageTimeout = setTimeout(myProject.Utils.createDelegate(this._changeImage, this), 2500);
}
I have provided a thorough detail because then it easier to understand the logic of the problem, and even if your not having the same issue, this may help you fingure out any problem.
I couldn't provide a JSfiddle, as i have created my CSS using Javascript, there are different ways of doing this, but i wanted to understand the logic behind the forward and reverse, and having a timer which continuously goes forward.
It seems like you want to cancel the animation on the slide (perhaps have it fade out while the next slide animates in, cancel its animation abruptly or let it finish and ignore the button click)
What I usually do, personally, is check for the animated state (yes, I use jquery, but you should be able to test the CSS or positioning values you are using to animate in the same way) you could even add an "active" class or data type during animation to make testing easier. Global flags work, too. If there is animation, ignore the button. (For my work... Depends on your intention)
Like I said, the problem may be with button behaviour not with the animation routine. It would be useful to see how you are calling this from the button click, and what your intended results are going to be.
How about CSS3 transitions?
transition: all 1s ease 0.5s;
Simple example on JS Fiddle.
This takes care of the animation, so you just need to set the intended destination using JavaScript, i.e.
this.style.left = '100px';
Or
this.style.top = '30px';
And CSS3 transitions will smoothly slide the element.
Cross Browser Note!
The transition property may need a vendor prefix for some browsers, I am using the latest production Firefox and you don't need -moz for that. Same goes for Opera, no '-o' required. Internet Exporer 10 needs no prefix. You may need to use -webkit for Safari / Chrome, but test without first.

Categories

Resources