How to get clicked position of div element for progressbar - javascript

I have a HTML5 Video panel in my app. I managed to make a progressbar which is working perfectly. But I also want to ability to change currentTime of the video by clicking to any position on progressbar.
How can I get current position as ratio using React? I searched progressbar components on google but it seems none of them give value of clicked position but just display.
Or simply, when user clicks on different position of the progressbar, I want to change/get value of progressbar to that position.
Update:
I have updated demo and it seems progressbar gone crazy when click multiple times and it seems not close to the clicked position either.
Demo: https://stackblitz.com/edit/react-ts-zudlbe?file=index.tsx

This is the whole component I tested it on.
Seems to work
const ProgressBar = ({ duration, progress }: ProgressProps) => {
return (
<div
onClick={(event: React.MouseEvent<HTMLDivElement>) => {
// duration is the video duration. For example 152.
// How can i catch the % (ratio) of clicked position of this progrssbar?
// then I will send this % (lets say 5) to the seek function like:
const x = (event.clientX * duration) / event.currentTarget.offsetWidth; // how to get sec?
callback(x);
}}
style={{
backgroundColor: "#ddd",
borderRadius: 3,
flex: 1,
height: 6,
overflow: "hidden",
position: "relative"
}}
>
<Progress progress={progress} />
</div>
);
};

Related

React scroll event doesn't stop firing

At work we're having some performance issues with rendering data tables so we've decided to try to virtualize a list "window". Essentially following the same idea as react-window, whereby you only render the sublist - the one showing on your viewport - of your data list.
For a myriad of reasons, we tried to implement the technique ourselves. In doing so, we learnt this is mostly done using position: absolute on each list item, which didn't really suitable for us. So we came up with the idea of just having two "wrapper" divs around the sublist we want to render.
Essentially box1 would have the height equal to the combined height of all the list items before our window and box2 would have the height of all the items after our window. Every time the user scrolls, we figure out which indices to render and adjust the box heights.
Unfortunately we ran into an issue where, when the user scrolls down, the scroll event keeps firing even after the user has stopped scrolling. This scrolls the list all the way to the end. It seems to work fine when scrolling up though, so we're really at a loss here. We couldn't figure out why it keeps firing.
Here's a link to an example. I just replaced all the list item logic with a fixed box for simplicity. I've also added a timeout to the scroll handler so the scrolling up behaviour is more noticeable, otherwise it's too fast and the red upper box is not noticeable.
Any help is much appreciated. Thanks in advance!
EDIT: We're actually using this in a <table> element, which means solutions based on css position property will not work, given that that property has undefined behaviour for table elements, and it breaks the standard table layout.
The problem is most likely caused by using the scrollTop value to change the height of the items which cause the scrollTop value to change and so on (maybe).
Here is the right way to do it https://codesandbox.io/s/react-hooks-playground-forked-97vsq
import React, { useState, useCallback } from "react";
import ReactDOM from "react-dom";
const ROW_HEIGHT = 25;
const App = (props) => {
const [items, innerHeight, onScroll] = useVirtualizedList({
numItems: 500,
itemHeight: ROW_HEIGHT,
windowHeight: ROW_HEIGHT * 5,
windowExtension: 0
});
return (
<div onScroll={onScroll} style={{ height: "500px", overflowY: "scroll" }}>
<div style={{
// position should be calculated depanding on the parent element position
position: 'fixed'
}}>{items}</div>
<div className="forceOverflow" style={{height: 500 * ROW_HEIGHT}}></div>
</div>
);
};
const useVirtualizedList = ({ numItems, itemHeight, windowHeight }) => {
const [scrollTop, setScrollTop] = useState(0);
const innerHeight = numItems * itemHeight;
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(
numItems - 1,
startIndex + Math.floor( windowHeight / itemHeight)
);
const onScroll = useCallback((e) => {
const currentScroll = e.currentTarget.scrollTop;
setScrollTop(currentScroll);
}, []);
const items = `${startIndex} --- ${endIndex}`
return [items, innerHeight, onScroll];
};
ReactDOM.render(<App />, document.getElementById("root"));

OnScroll Fold the View - React Native

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

why scroll down navigation bar is not changing the color?

here is my sample code
in the browser i want to scroll down the page the the navbar will say what color i am showing.
<div style={{height: "800px"}}>
<h2 style={{backgroundColor: `${nav}`,
position: "fixed",
width: "100%"
}}
>
NaveBar {nav ? "red" : "blue"}!
</h2>
</div>
it's somewhat not changing the name of the title and color also.i just dun know where is the problem.
can somebody help me on this please?
You initialized the state with a string useState("red");
and then you update the state to an object with setNav({ back });
To solve this just change it to setNav(back)
By the way - listening to scroll-events can be laggy, so you might want to "throttle" the event.
import throttle from lodash or just copy paste this function:
https://gist.github.com/abhinavnigam2207/a147abe0213d60467abacd33db7c6d2e
Then you use it by wrapping your function into it, like this:
useEffect(() => {
window.addEventListener(
"scroll",
throttle(() => {
const back = window.scrollY < 70 ? "red" : "blue";
setNav(back);
}, 100)
);
});

React animation for moving an element from one parent to another

I am trying to create an animation for moving a child element from one parent element to another using React.
A user should be able to click on an element and see it move into another div.
I made a simple demo component (without the animation) to show what I mean. When an element is clicked, the state updates and the elements are re-rendered in the correct place.
class App extends React.Component {
state = {
list: ['Alice', 'Bob', 'Charlie', 'David', 'Emily', 'Frank'],
top: [0, 1, 2],
bottom: [3, 4, 5]
}
moveDown = (item) => {
let { top, bottom } = this.state
this.setState({
top: top.filter(x => x !== item),
bottom: [...bottom, item]
})
}
moveUp = (item) => {
let { top, bottom } = this.state
this.setState({
top: [...top, item],
bottom: bottom.filter(x => x !== item)
})
}
render() {
let { top, bottom, list } = this.state
return (
<div style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
alignItems: 'center',
height: '90vh',
width: '100%'
}}>
<div>
{top.map((item) =>
<div
onClick={() => this.moveDown(item)}
style={{color:'red'}}>{list[item]}</div>
)}
</div>
<div>
{bottom.map((item) =>
<div
onClick={() => this.moveUp(item)}
style={{color:'green'}}>{list[item]}</div>
)}
</div>
</div>
)
}
}
Codepen demo: https://codepen.io/ee92/pen/LqrBjL?editors=0010
Big appreciation to and thanks in advance for any help or advice on how to achieve this div-to-div animation.
No it's not possible
It's not possible to animate in that way because the DOM thinks you're removing a div and then adding a new div. Even though it's the same div to you, the DOM doesn't have that context. Animations are controlled by changes to CSS, not HTML.
...but here's how to do it
If you actually need both lists to stay in different divs the best you can do is either:
Animate the old item to the new item position, then delete the old item and show the new item.
Remove the old item and create a new item where the old item was and move it to the new item position.
Same concept, two ways of doing it.
I modified your existing sample to show a simplified version of option 2. Note that there are a number of animation decisions to make like what happens when the list gets smaller, how should the items change from red to green, etc., and I didn't try and objectively solve them. Also, this would be much easier if you could have all the items for both lists in one div, and control their positions absolutely. But if they need to end up in separate divs...
https://codepen.io/sallf/pen/VgBwQr?editors=0010
What's going on
Adding a transition to .item we can make the animation happen when we make adjustments to the transform property.
On item click we update our lists in state and add...
transition.item to know which item is animating...
transition.startTop to know the offset y position the item should start at relative to the bottom of the list it's moving to, and...
transition.startAnim as a flag to control the animation.
Since transitions need something to change before they'll animate, we use setTimeout to delay the change of transition.startAnim which basically causes the animation from the computed position, back to 0.

Slide boxes with margin-left check if overslided

I made a simple content/box slider which uses the following javascript:
$('#left').click(function () {
$('#videos').animate({
marginLeft: '-=800px'
}, 500);
});
$('#right').click(function () {
$('#videos').animate({
marginLeft: '+=800px'
}, 500);
});
Here is the demo: http://jsfiddle.net/tjset/2/
What I want to do and I can't figure out how to show and hide arrows(left and right box) as the all the boxes slided.
So I clicked 4 time to the LEFT and slided all the boxes! then hide "left" so that you can't give more -800px
What can I do?
What you can do is check after the animation completes to see if the margin-left property is smaller or larger than the bounds of the video <div>. If it is, depending on which navigation button was clicked, hide the appropriate navigation link.
Check out the code below:
$('#left').click(function () {
// reset the #right navigation button to show
$('#right').show();
$('#videos').animate({
marginLeft: '-=800px'
}, 500, 'linear', function(){
// grab the margin-left property
var mLeft = parseInt($('#videos').css('marginLeft'));
// store the width of the #video div
// invert the number since the margin left is a negative value
var videoWidth = $('#videos').width() * -1;
// if the left margin that is set is less than the videoWidth var,
// hide the #left navigation. Otherwise, keep it shown
if(mLeft < videoWidth){
$('#left').hide();
} else {
$('#left').show();
}
});
});
// do similar things if the right button is clicked
$('#right').click(function () {
$('#left').show();
$('#videos').animate({
marginLeft: '+=800px'
}, 500, 'linear', function(){
var mRight = parseInt($('#videos').css('marginLeft'));
if(mRight > 100){
$('#right').hide();
} else {
$('#right').show();
}
});
});
Check out the jsfiddle:
http://jsfiddle.net/dnVYW/1/
There are many jQuery plugins for this. First determine how many results there are, then determine how many you want visible, then use another variable to keep track with how many are hidden to the left and how many are hidden to the right. So...
var total = TOTAL_RESULTS;
var leftScrolled = 0;
var rightScrolled = total - 3; // minus 3, since you want 3 displayed at a time.
instead of using marginLeft I would wrap all of these inside of a wrapper and set the positions to absolute. Then animate using "left" property or "right". There's a lot of code required to do this, well not MUCH, but since there are many plugins, I think you'd be better off searching jquery.com for a plugin and look for examples on how to do this. marginLeft is just not the way to go, since it can cause many viewing problems depending on what version of browser you are using.

Categories

Resources