I am creating a react application where I am using three videos with custom progress bar for each one to be displayed at screen. I am facing a problem with progress bar update for all the three videos. When one video is fully played it's progress works fine but as soon as second video start playing, It also updates the bar width together (video1 and video2). I am using "timeupdate" event for updating the video progress bar.
`
import * as React from 'react';
import './style.css';
export default function App() {
var v3 = `http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4`;
var v2 = `http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4`;
var v1 = `http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerFun.mp4`;
const [id, setId] = React.useState(1);
const [currentVideo, setCurrentVideo] = React.useState('');
const [barCompleted, setBarCompleted] = React.useState<any>({
myBar1: false,
myBar2: false,
myBar3: false,
});
const videoRef = React.useRef<any>();
const progress1 = React.useRef<any>();
const progress2 = React.useRef<any>();
const progress3 = React.useRef<any>();
const myBar1 = React.useRef<any>();
const myBar2 = React.useRef<any>();
const myBar3 = React.useRef<any>();
const handleOnEnded = () => {
videoRef.current.removeEventListener('timeupdate', () => {});
// setBarCompleted(prev=>{myBar1:true,...prev})
setId((prev) => prev + 1);
};
const getVideoById = (id) => {
if (id == 1) {
setCurrentVideo(v1);
} else if (id == 2) {
setCurrentVideo(v2);
} else if (id == 3) {
setCurrentVideo(v3);
}
};
React.useEffect(() => {
getVideoById(id);
}, [id]);
React.useEffect(() => {
videoRef?.current?.load();
configureProgress(id);
}, [currentVideo, id]);
const configureProgress = (id) => {
videoRef?.current?.addEventListener('timeupdate', (e) => {
// console.log(e.target.currentTime);
let i = 0;
if (i == 0) {
i = 1;
// var elem = document.getElementById('myBar1');
let width = 1;
let sid = setInterval(frame, 1000);
function frame() {
if (videoRef.current.ended || width >= 100) {
clearInterval(sid);
i = 0;
if (id == 1) {
progress1.current.style.backgroundColor = 'green';
myBar1.current.style.width = 0 + '%';
} else if (id == 2) {
progress2.current.style.backgroundColor = 'green';
myBar2.current.style.width = 0 + '%';
} else if (id == 3) {
progress3.current.style.backgroundColor = 'green';
myBar3.current.style.width = 0 + '%';
}
} else {
width = Math.ceil(
(videoRef.current.currentTime / videoRef.current.duration) * 100
);
if (id == 1) {
myBar1.current.style.width = width + '%';
console.log('width update id1');
} else if (id == 2) {
myBar2.current.style.width = width + '%';
console.log('width update id2');
} else if (id == 3) {
myBar3.current.style.width = width + '%';
console.log('width update id3');
}
}
}
}
});
};
return (
<div>
<div className="progress-container">
<div id="myProgress1" ref={progress1}>
<div id="myBar1" ref={myBar1}></div>
</div>
<div id="myProgress2" ref={progress2}>
<div id="myBar2" ref={myBar2}></div>
</div>
<div id="myProgress3" ref={progress3}>
<div id="myBar3" ref={myBar3}></div>
</div>
</div>
{currentVideo && (
<video
autoPlay={true}
controls
muted={true}
id="myVideo1"
width={300}
height={250}
ref={videoRef}
crossOrigin="anonymous"
onEnded={handleOnEnded}
preload="metadata"
playsInline
>
<source src={currentVideo} type="video/mp4" />
</video>
)}
</div>
);
}
`
.progress-container {
display: flex;
}
#myProgress1,
#myProgress2,
#myProgress3 {
width: 33%;
background-color: #ddd;
}
#myBar1,
#myBar2,
#myBar3 {
width: 1%;
height: 30px;
background-color: #04aa6d;
}
[enter image description here](https://i.stack.imgur.com/usYQq.png)
I am expecting that "timeupdate" event should not run for second video as it also updates width for previous video(1st video).
Related
I am having troubles fixing a bug basically I need to iterate over a list of buttons and apply an animation and on the next iteration I remove the animation from the previous element, however, when running the code the animation is started twice at the beginning and one element remains stuck with the animation applied.
The following is the code of the component:
import type { NextPage } from 'next'
import Head from 'next/head'
import { useRouter } from 'next/router'
import { useEffect, useRef, useState } from 'react'
import { IoCheckmark, IoClose, IoHome, IoRefresh } from 'react-icons/io5'
import Page from '../components/page/Page'
import styles from '../styles/Play.module.css'
import { distance } from '../utils/distance'
import { randomInt, randomFloat } from '../utils/random'
function ShowSequence(props: any) {
const [index, setIndex] = useState(0);
const [sequence, setSequence] = useState(props.sequence);
const [timer, setTimer] = useState<any>();
useEffect(() => {
console.log(index)
if (index > 0) document.getElementById(sequence[index - 1])?.classList.toggle(styles.animate);
if (index < sequence.length) document.getElementById(sequence[index])?.classList.toggle(styles.animate);
else return clearInterval(timer);
setTimer(setTimeout(() => setIndex(index + 1), 3000));
}, [index]);
return <div className={styles.button}>
{
props.map ? props.map.map((button: any) => {
return <button key={button.buttonId} className={styles.button} id={button.buttonId} style={{ top: button.y + "px", left: button.x + "px", backgroundColor: button.color }}></button>
}) : null
}
</div>;
}
function DoTask(props: any) {
return <div>
</div>;
}
function ChooseSequence(props: any) {
const [sequence, setSequence] = useState(props.sequence);
const [index, setIndex] = useState(0);
const [timer, setTimer] = useState<any>();
const [buttonMap, setButtonMap] = useState<any>({});
console.log(sequence);
return <div className={styles.button}>
{
props.map ? props.map.map((button: any) => {
return <button key={button.buttonId} className={styles.button} id={button.buttonId} style={{ top: button.y + "px", left: button.x + "px", backgroundColor: button.color }} onClick={(e) => {
let correctSequence = sequence[index] === button.buttonId;
e.currentTarget.classList.toggle(correctSequence ? styles.correctButton : styles.wrongButton);
buttonMap[button.buttonId] = correctSequence ? <IoCheckmark size={20} color={"white"}></IoCheckmark> : <IoClose size={20} color={"white"}></IoClose>;
setButtonMap(buttonMap);
setIndex(index + 1);
}}>
{ (buttonMap[button.buttonId]) ? buttonMap[button.buttonId] : button.buttonId }
</button>
}) : null
}
</div>;
}
function Error(props: any) {
return <div className={styles.errorMenu}>
<h1>You lost!</h1>
<p>You reached level: {props.level}</p>
<div className={styles.container}>
<div className={styles.item}></div>
<div className={styles.item}></div>
<div className={styles.item}></div>
<div className={styles.item}></div>
<div className={styles.item}></div>
<div className={styles.item}></div>
<div className={styles.item}></div>
<div className={styles.item}></div>
<div className={styles.item}></div>
<div className={styles.item}></div>
</div>
<div className={styles.row}>
<button className={styles.retryButton} onClick={() => window.location.href = "/play"}><IoRefresh></IoRefresh></button>
<button className={styles.closeButton} onClick={() => window.location.href = "/"}><IoHome></IoHome></button>
</div>
</div>;
}
enum State {
SHOWSEQUENCE,
DOTASK,
CHOOSESEQUENCE,
ERROR
}
const Play: NextPage = () => {
let [state, setState] = useState<State>(State.SHOWSEQUENCE);
let [sequence, setSequence] = useState<number[]>([randomInt(1, 20), randomInt(1, 20), randomInt(1, 20), randomInt(1, 20)]);
let [map, setMap] = useState<any[]>();
let [level, setLevel] = useState(1);
let component;
useEffect(() => {
if (state === State.SHOWSEQUENCE) {
let newSequenceId = randomInt(1, 20);
setSequence((prevSequence: number[]) => [...prevSequence, newSequenceId])
}
}, [state]);
useEffect(() => {
let buttonIds = Array.from({ length: 20 }, (v, k) => k + 1);
const { innerWidth, innerHeight } = window;
let colors: string[] = ["#c0392b", "#e67e22", "#27ae60", "#8e44ad", "#2c3e50"];
let buttonMap: any[] = [];
let rows = buttonIds.length / 10;
let columns = rows > 0 ? buttonIds.length / rows : buttonIds.length;
for (let row = 0; row < rows; row++) {
for (let col = 0; col < columns; col++) {
let color = colors[Math.floor(randomFloat() * colors.length)];
let x = innerWidth / columns * col + 100;
let y = innerHeight / rows * row + 100;
let offsetX = (randomFloat() < .5) ? -1 : 1 * randomFloat() * ((innerWidth / columns) - 100);
let offsetY = (randomFloat() < .5) ? -1 : 1 * randomFloat() * ((innerHeight / rows) - 100);
if (x + offsetX + 100 > innerWidth) offsetX -= ((x + offsetX) - innerWidth) + 100;
if (y + offsetY + 100 > innerHeight) offsetY -= ((y + offsetY) - innerHeight) + 100;
buttonMap.push({ buttonId: buttonIds[row * columns + col], x: x + offsetX, y: y + offsetY, color })
}
}
setMap(buttonMap);
}, [])
switch (state) {
case State.SHOWSEQUENCE:
component = <ShowSequence map={map} sequence={sequence} changeState={() => setState(State.DOTASK)}></ShowSequence>;
break;
case State.DOTASK:
component = <DoTask changeState={() => setState(State.CHOOSESEQUENCE)} onError={() => setState(State.ERROR)}></DoTask>
break;
case State.CHOOSESEQUENCE:
component = <ChooseSequence map={map} sequence={sequence} changeState={() => setState(State.SHOWSEQUENCE)} onError={() => setState(State.ERROR)}></ChooseSequence>
break;
}
return (
<Page color="blue">
{ state === State.ERROR ? <Error level={level}></Error> : null }
{component}
</Page>
)
}
export default Play
Here is a codesandbox.
Instead of querying the DOM, an anti-pattern in React, you should add the appropriate classname when mapping the data. This avoids the DOM mutations and handles the conditional logic for adding and removing the "animate" class.
Use a functional state update to increment the index.
I suggest using a React ref to hold a reference to the interval timer so it's not triggering additional rerenders
Return a cleanup function to clear any running timers when necessary, i.e. when the component unmounts.
Code:
function ShowSequence(props) {
const [index, setIndex] = useState(0);
const timerRef = useRef();
useEffect(() => {
console.log(index);
timerRef.current = setTimeout(() => setIndex((index) => index + 1), 3000);
return () => {
clearTimeout(timerRef.current);
};
}, [index]);
return (
<div className={styles.button}>
{props.map?.map((button, i) => {
return (
<button
key={button.buttonId}
className={[styles.button, i === index ? styles.animate : null]
.filter(Boolean)
.join(" ")}
id={button.buttonId}
style={{
top: button.y + "px",
left: button.x + "px",
backgroundColor: button.color
}}
></button>
);
})}
</div>
);
}
To cleanup the timer in the useEffect, you must return a function.
The previous element index is 1 less the current index for non-zero indexes or the last element in the array of buttons when the current index is the first item.
const prevElIndex = index == 0 ? props.map.length - 1 : index - 1;
Also, you need to check if the previous element has the animation class before toggling the class. This takes care of the first time the animation starts to run (the previous element would not have the animation class).
if (
document
.getElementById(sequence[prevElIndex])
?.classList.contains(styles.animate)
) {
document
.getElementById(sequence[prevElIndex])
?.classList.toggle(styles.animate);
}
Altogether, your effect would be along these lines:
const [index, setIndex] = useState(0);
const [sequence, setSequence] = useState(props.sequence);
const [timer, setTimer] = useState<number>();
useEffect(() => {
const prevElIndex = index == 0 ? props.map.length - 1 : index - 1;
if (
document
.getElementById(sequence[prevElIndex])
?.classList.contains(styles.animate)
) {
document
.getElementById(sequence[prevElIndex])
?.classList.toggle(styles.animate);
}
document.getElementById(sequence[index])?.classList.toggle(styles.animate);
setTimer(setTimeout(() => setIndex((index + 1) % sequence.length), 3000));
return () => clearTimeout(timer);
}, [index]);
A working Stackblitz showing this in action.
The loop works only once and then nothing happens. I have three testimonials, and can go only once forward or backwords.Thanks for help!
const nextBtn = document.querySelector(".next-btn");
const prevBtn = document.querySelector(".prev-btn");
const testimonials = document.querySelectorAll(".testimonial");
let index = 0;
window.addEventListener("DOMContentLoaded", function () {
show(index);
});
function show(index) {
testimonials.forEach((testimonial) => {
testimonial.style.display = "none";
});
testimonials[index].style.display = "flex";
}
nextBtn.addEventListener("click", function () {
index++;
if (index > testimonials.length - 1) {
index = 0;
}
show(index);
});
prevBtn.addEventListener("click", function () {
index--;
if (index < 0) {
index = testimonials.length - 1;
}
show(index);
});
I would use a "hidden" class to hide the non-active testimonials instead of manipulating the element's style in-line. Also, your navigation logic can be simplified to a modulo operation.
The code your originally posted seemed to work out well, but it seems to cluttered with redundancy (code reuse). It also lacks structural flow (readability).
const
modulo = (n, m) => (m + n) % m,
moduloWithOffset = (n, m, o) => modulo(n + o, m);
const
nextBtn = document.querySelector('.next-btn'),
prevBtn = document.querySelector('.prev-btn'),
testimonials = document.querySelectorAll('.testimonial');
let index = 0;
const show = (index) => {
testimonials.forEach((testimonial, currIndex) => {
testimonial.classList.toggle('hidden', currIndex !== index)
});
}
const navigate = (amount) => {
index = moduloWithOffset(index, testimonials.length, amount);
show(index);
}
// Create handlers
const onLoad = (e) => show(index);
const onPrevClick = (e) => navigate(-1);
const onNextClick = (e) => navigate(1);
// Add handlers
window.addEventListener('DOMContentLoaded', onLoad);
nextBtn.addEventListener('click', onNextClick);
prevBtn.addEventListener('click', onPrevClick);
.testimonial {
display: flex;
}
.testimonial.hidden {
display: none;
}
<div>
<button class="prev-btn">Prev</button>
<button class="next-btn">Next</button>
</div>
<div>
<div class="testimonial">A</div>
<div class="testimonial">B</div>
<div class="testimonial">C</div>
<div class="testimonial">D</div>
<div class="testimonial">E</div>
<div class="testimonial">F</div>
</div>
UPDATE: I have figured out a muuuuch simpler workaround by sing in the typescript file so the JS parent is no longer needed. ~facepalm~ Thanks for all your suggestions!
I am trying to get a button to trigger the function affTimer() inside the child function component but I keep getting the error "this is undefined" in relation to the function call. Here is the two code files:
affType.js
import React, {Component} from 'react';
import ReactPlayer from 'react-player'
import { Link } from 'react-router-dom';
import affirmationService from '../Services/requestService'
import affTrack from '../audio/inner.wav';
import warn from '../audio/warning.wav';
import Player from '../Player/Player';
import videoBG from '../videos/InnerStrength.mp4';
import Type from '../Type/Type.tsx';
import Button from "../customButton";
import {tXP} from '../Type/Type.tsx';
class affType extends Component {
constructor(props) {
super(props);
this.state = {character: undefined};
this.child = React.forwardRef();
this.startGame = this.startGame.bind(this);
}
async componentDidMount() {
const { match: { params } } = this.props;
//let affirmation_id = params.affirmation_id;
//let response = await affirmationService.getById(affirmation_id);
//this.setState({character: response.data});
setTimeout(() => {
document.getElementById('overlay_blk_fast').style.opacity = 0;
setTimeout(() => {
document.getElementById('overlay_blk_fast').style.display = 'none';
}, 1000);
}, 10);
}
spawnDialog() {
document.getElementById('overlay_1').style.display = 'block';
setTimeout(() => {
document.getElementById('overlay_1').style.opacity = 1;
}, 10);
}
destroyDialog() {
document.getElementById('overlay_1').style.opacity = 0;
setTimeout(() => {
document.getElementById('overlay_1').style.display = 'none';
}, 1000);
}
repeat() {
document.getElementById('overlay_2').style.opacity = 0;
document.querySelector('video').play();
setTimeout(() => {
document.getElementById('overlay_2').style.display = 'none';
}, 1000);
}
test_ended() {
document.getElementById('overlay_2').style.display = 'block';
setTimeout(() => {
document.getElementById('audio_end').play();
document.getElementById('overlay_2').style.opacity = 1;
}, 10);
}
startGame() {
var track = document.getElementById('aff');
track.play();
this.child.current.affTimer();
}
render() {
return (
<div>
<div className="contentplayer">
<audio id='aff'><source src={affTrack} /></audio>
<video autoPlay muted loop id="myVideo">
<source src={videoBG} type="video/mp4" />
</video>
<audio id="audio_end" src="/Audio/Inner Strength completed quest - play with completed quest prompt.wav"/>
</div>
<p>{tXP}</p>
<Button
border="none"
color="pink"
height = "200px"
onClick={this.startGame}
radius = "50%"
width = "200px"
children = "Start!"
/>
<Type ref={this.child}>
</Type>
<div className="aligntopright" onClick={() => {this.spawnDialog()}}>
<div className="backbtn-white"></div>
</div>
<div className="overlay_blk_fast" id="overlay_blk_fast"></div>
<div className="overlay" id="overlay_1">
<div className="dialog">
<div className="dialogcontainer">
<img className="dialogbg"/>
<h3 className="dialogtext">Are you sure you would like to go back to the selection page?</h3>
<h2 className="no" onClick={() => {this.destroyDialog()}}>No</h2>
<Link to="/affirmation"><h2 className="yes">Yes</h2></Link>
</div>
</div>
</div>
<div className="overlay" id="overlay_2">
<div className="dialog">
<div className="dialogcontainer">
<img className="dialogbg"/>
<h3 className="dialogtext">Would you like to repeat this quest?</h3>
<Link to="/affirmation"><h2 className="no">Go back</h2></Link>
<h2 className="yes" onClick={() => {this.repeat()}}>Repeat</h2>
</div>
</div>
</div>
</div>
)
}
}
export default affType;
type.tsx
import React, {Component} from 'react';
import useTypingGame from "react-typing-game-hook";
import { textSpanContainsTextSpan } from 'typescript';
var xpM = 0;
var i = 0;
var err = 0;
var xp = 5;
var tXP = 0;
var addXP = 1;
var bonus = 0;
var bonusCounter = 0;
//var warnP = new Audio({warn});
//var affTrackP = new Audio('../audio/inner.wav');
function TypeF() {
let text_array = [
"There is strength and solidity within me",
"Courage is flooding through my veins",
"I possess strength within my heart",
"I am leading the charge with courage, and a vigorous resolution",
"There is a force inside me that is unbelievably powerful",
"There is a brave, radiant spirit inside me",
"I am a tall tree, with thick and strong roots",
"I was born for this",
"There is a divinity within",
"I am a force of nature",
"I possess the mental fortitude of those who climb the highest peaks",
"I was born with a determined spirit",
"There is an intensity in my eyes"
];
let text = text_array[i];
const {
states: {
charsState,
length,
currIndex,
currChar,
correctChar,
errorChar,
phase,
startTime,
endTime
},
actions: { insertTyping, resetTyping, deleteTyping }
} = useTypingGame(text);
const handleKey = (key: any) => {
if (key === "Escape") {
resetTyping();
} else if (key === "Backspace") {
deleteTyping(false);
} else if (key.length === 1) {
insertTyping(key);
}
};
if (currIndex + 1 === length) {
xpM = xpM + 1;
bonusCounter = bonusCounter + 1;
err = err + errorChar;
addXP = ((xp * correctChar) - (err * 2)) * xpM;
if (err > correctChar) {
addXP = correctChar * 3;
}
tXP = tXP + addXP;
if (bonusCounter >= 5) {
bonus = bonus + 1;
bonusCounter = 0;
}
resetTyping();
}
var tmr;
var cd = 18;
function affTimer() {
tmr = setInterval(tock, 1000);
if (i >= text_array.length) {
clearInterval(tmr);
}
}
function tock() {
if (cd > 0) {
cd = cd - 1;
console.log(cd);
}
else if (cd <= 0) {
if (i < text_array.length) {
i = i + 1;
cd = 18;
resetTyping();
}
else {
i = text_array.length;
}
}
}
return (
<div className='container'>
<div
className="typing-test"
id="start"
onKeyDown={(e) => {
handleKey(e.key);
e.preventDefault();
}
}
tabIndex={0}
>
{text.split("").map((char: string, index: number) => {
let state = charsState[index];
let color = state === 0 ? "white" : state === 1 ? "green" : "red";
return (
<span
key={char + index}
style={{ color }}
className={currIndex + 1 === index ? "curr-letter" : ""}
>
{char}
</span>
);
})}
</div>
<h2 className='debug'> TIMER: {cd}, I: {i}, ERRORS: {err}, MULTIPLIER: {xpM}, Type XP: {correctChar * xp}, CurrXP: {correctChar * xp * xpM} XPTotal: {tXP} bonusCounter: {bonusCounter}, BONUS: {bonus}</h2>
</div>
);
}
export {tXP};
export default TypeF;
Any help would be amazing, I have been stuck on this for 2 days and it is the last bit I need to complete so I can move to the next phase.
Your child component is a function component. You can't get a ref to an instance of a function component, because there is no instance. If you really need to use a function component and also expose some custom object as a ref, then you can use the useImperativeHandle hook plus forwardRef to define what the parent component should receive on its ref. For example:
const Type = React.forwardRef((props, ref) => {
// ...
useImperativeHandle(ref, () => {
// The following object is what will get assigned to the
// parent component's this.child.current
return {
afftimer: function () {
tmr = setInterval(tock, 1000);
if (i >= text_array.length) {
clearInterval(tmr);
}
}
}
});
// ...
})
But useImperativeHandle is not a common thing to use. There is likely a more standard way to solve your problem. The normal way for a parent component to tell a child component what to do is with props, not with refs.
My JS page
const catImage=document.querySelector('img')
let catImageLeft=catImage.style.left='0px'
function catwalk() {
let position= 0
setInterval(frame,50)
function frame() {
position+=10
catImage.style.left=position+'px'
if (position==1080) {
return catwalk()
}
if (position==540) {
setTimeout(catImage.src='https://media1.tenor.com/images/2de63e950fb254920054f9bd081e8157/tenor.gif',1000)
}
}
}
As you can see, it keeps on moving after replacing the image. And also if i would like the image to start over again, it stutters.
any suggestions.
Try this!
const catImage = document.querySelector('img');
const catWalkimg = 'https://i.imgur.com/Akh489w.gif';
const catDancImg = 'https://media2.giphy.com/media/3mXcJgVhPxWZa/giphy.gif';
catwalk();
function catwalk() {
let position = 0;
setInterval(function() {
position = position > 700 ? 0 : position;
if (position == 350) {
if (catImage.src != catDancImg) {
catImage.src = catDancImg;
setTimeout(() => {
catImage.src = catWalkimg;
position += 10;
}, 2000);
}
} else {
catImage.style.left = position + 'px';
position += 10;
}
}, 50);
}
img {width: 160px}
<img style="position:absolute;" src="https://i.imgur.com/Akh489w.gif" />
I'm trying to implement a virtual list in the same vein as React-Virtualized.
I've got it working for the most part however when you scroll past a certain threshold, the list continues to scroll by itself without user input, until it reaches the end of the list. What am I doing that causes this?
Here's a Codepen.
The Component in question is the VirtualList component.
const todoHeight = 40;
const viewport = { height: 800, width: 750 };
const itemsAllowedInView = viewport.height / todoHeight;
const numItems = 10000;
const maxScrollItems = numItems - itemsAllowedInView;
const maxScrollTop = todoHeight * maxScrollItems;
class VirtualList extends React.Component {
constructor(props) {
super(props);
this.state = {
currentIndex: 0,
amountRowsToRender: itemsAllowedInView * 2,
topPadding: 0,
bottomPadding: maxScrollTop - viewport.height
}
this.lastTop = 0;
}
scrollFunction(currentTop) {
if (this.lastTop === currentTop || currentTop >= maxScrollTop || currentTop < 0) { return; }
let currentIndex = Math.floor(currentTop / todoHeight);
let endIndex = Math.min(currentIndex + itemsAllowedInView, numItems);
// We want to render some extra for padding.
let bufferItems = itemsAllowedInView / 2;
let bufferSize = bufferItems * todoHeight;
// Create top Padding
let topPaddingHeight = 0;
let bottomPaddingHeight = 0;
topPaddingHeight = Math.max(0, currentTop - (bufferItems * todoHeight));
// bottomPaddingHeight = Math.min((currentTop - (bufferItems * todoHeight)) + itemsAllowedInView, maxScrollTop);
bottomPaddingHeight = Math.min(maxScrollTop - currentTop - viewport.height - (bufferItems * todoHeight), maxScrollTop);
this.lastTop = currentTop;
this.setState({
currentIndex: currentIndex,
topPadding: topPaddingHeight >= 0 ? topPaddingHeight : 0,
bottomPadding: bottomPaddingHeight >= 0 ? bottomPaddingHeight : 0,
})
}
catchScroll(e) {
this.scrollFunction.call(this, e.target.scrollTop);
}
paddingDiv(height) {
return (<div className='todo' style={{ height: height + 'px' }} />);
}
paddingTop() {
// if(this.state.topPadding > 0){
return (this.paddingDiv(this.state.topPadding));
// }
}
paddingBottom() {
// if(this.state.bottomPadding > 0){
return (this.paddingDiv(this.state.bottomPadding));
// }
}
render() {
return (
<div
className={'list-container'}
onScroll={this.catchScroll.bind(this)}
>
<div className={'inner-list'}>
{this.paddingTop.call(this)}
{!!this.props.todos && this.props.todos.slice(this.state.currentIndex, this.state.currentIndex + this.state.amountRowsToRender).map((todo, i) =>
<Todo content={todo.content}
height={todo.height ? todo.height : null}
id={i}
key={i}
completed={todo.completed}
/>
)}
{this.paddingBottom.call(this)}
</div>
</div>
)
}
}