In Slick slider i'm using the below configuration
https://kenwheeler.github.io/slick/
autoplay: true,
autoplaySpeed: 6000,
pauseOnHover: true,
pauseOnFocus: true,
pauseOnDotsHover: true,
Here, when we hover on the banner its pausing and when mouse leave again the timer is running 6000ms again and moving to next slide.
I need like,
if we mouse hover on 3rd sec and keep some more seconds and leave then,
the remaining 3 seconds only should stop and play the next slide.
How to achieve this in slick slider please.
Finally done in React slick slide
We need to consider the mouse in and out time and calculate the remaining time (even in multiple time if the user did the mouse in and out)
In slick slide there is a option, afterChange and init
init: () => {
setSliderInitialized(true)
addTimeStamp()
},
afterChange: index => {
setCurrentSlide(index)
setAutoplaySpeed(parseInt(props.timer)) // props.timer is pause time which is from JSON
//setAutoplaySpeed(6000) - 6 sec
setTimeStamps([new Date()])
},
and have to consider the mouse in and out time, for this from React can import useEffect
import React, { useEffect, useRef, useState } from "react"
const slider = useRef()
const [hovered, setHovered] = useState(false)
const previousHovered = usePrevious(hovered)
const [currentSlide, setCurrentSlide] = useState(-1)
const [autoplaySpeed, setAutoplaySpeed] = useState(parseInt(props.timer))
const [timeStamps, setTimeStamps] = useState([])
const [sliderInitialized, setSliderInitialized] = useState(false)
const addTimeStamp = () => setTimeStamps([...timeStamps, new Date()])
need to use the time stamp
const getElapsedTime = () => {
let elapsedTime = 0
for (let i = 0; i < timeStamps.length; i += 2) {
const start = timeStamps[i]
const stop = timeStamps[i + 1]
elapsedTime += stop - start
}
return elapsedTime
}
useEffect(() => {
if (previousHovered === false && hovered === true) {
addTimeStamp()
}
if (previousHovered === true && hovered === false) {
addTimeStamp()
const elapsedTime = getElapsedTime()
let remainingTime = parseInt(props.timer) - elapsedTime // props.timer - 6000ms
setAutoplaySpeed(remainingTime ? remainingTime : parseInt(props.timer))
}
}, [hovered])
When the slider initialized (init) and changed afterChange - pass the timer.
When mouse hover store the current time and and mouse out store the time in **array**
By iterating the array and subtract we can get the elapsedTime. And we need to change the pause time from 6000ms to elapsed Time. and when slider change again we need to set the 6000ms.
Related
I want to play a video with the setInterval function which should set the currentTime every Frame to play the video at the usual speed. My video has 25 frames per second. And to make it smoother I thought of using requestAnimationFrame, as without it it's lagging a bit. But I'm lost in the React useEffect Architecture on how to unmount all different life cycles in the right way. So how to simultaneously use setInterval with requestAnimationFrame to set the currentTime correctly.
The comments show
import React, { useState, useEffect, useRef } from "react";
export default function VideoInterval() {
const videoRef = useRef(null);
const [currentFrame, setCurrentFrame] = useState(0);
useEffect(() => {
// Set Video Framerate
const framerate = 25;
// Calculate interval duration for fluid playback
const intervalDuration = 1000 / framerate;
const interval = setInterval(() => {
// Not sure if it's a bad idea to store it in a let variable or this will cause problems
// let currentTime = videoRef.current.currentTime;
// Use requestAnimationFrame for smooth playback
const intervalPlay = () => {
videoRef.current.currentTime =
videoRef.current.currentTime + intervalDuration / 1000; // add interval in seconds
// window.requestAnimationFrame(intervalPlay);
};
window.requestAnimationFrame(intervalPlay);
// Tried it over useState to see if working with refs made it slower that useState
// setCurrentFrame((i) => i + intervalDuration / 1000);
// Not sure where or if to cancel the AnimationFrame
// cancelAnimationFrame(videoRef.current.currentTime);
}, intervalDuration); // every 40 milliseconds == 25 frames per second
return () => {
clearInterval(interval);
};
}, []);
return (
<>
<video
ref={videoRef}
// autoPlay
muted
loop
>
<source src="video_with_25_frames_per_second.mp4" type="video/mp4" />
</video>
</>
);
}
I'm working on a Pomodoro clock. To build the countdowns I'm using useEffect and setTimeout. Everything seemed to be fine until I realized there's a 30ms to 50ms delay between every second of the clock. How would I set it to pricesily update the clock at every 1000ms?
I'm using useState to handle the clock's time and controls to stop, pause and reset. They're all working properly. It's just the second's timing that is delaying more that it should.
function App() {
const [workTarget, setWorkTarget] = useState(25 * 60);
const [breakTarget, setBreakTarget] = useState(5 * 60);
const [time, setTime] = useState(workTarget); //time in seconds
const [counting, setCounting] = useState(false);
const [working, setWorking] = useState(true);
const [clockTarget, setClockTarget] = useState(workTarget);
const [combo, setCombo] = useState(0);
const [config, setConfig] = useState(false);
const [playWork] = useSound(workSfx);
const [playBreak] = useSound(breakSfx);
const [playPause] = useSound(pauseSfx);
let tick = 1000;
let timeout;
let timenow = Date.now();
// Handle pause and stop of countdown
useEffect(() => {
if (time > 0 && counting === true) {
timeout = setTimeout(() => {
setTime(time - 1);
console.log(timenow);
}, tick);
} else if (time === 0 && counting === true) {
setWorking(!working);
if (working === true) {
playBreak();
setTime(breakTarget);
setClockTarget(breakTarget);
} else {
playWork();
setCombo(combo + 1);
setTime(workTarget);
setClockTarget(workTarget);
}
}
if (!counting || config) {
clearTimeout(timeout);
}
});
}
export default App;
This is not the complete code. I cut off other components for buttons and stuff that don't relate to this.
We can't ensure its EXACTLY 1000 ms. Behind the scenes, react state updates use setTimeout, and to change the timer, you need to use either setTimeout or setInterval. setTimeout and setInterval only ensure that the code inside will not run for a delay period, and then executes when the main thread is not busy.
Therefore, it's impossible to ensure that every update is EXACTLY 1000ms. There will usually be 30-50ms delay.
However, that doesn't mean your timer will be inaccurate or unreliable. It just depends how you initialize it.
Below is how I would improve the code you provided, as right now clearing the timeout adds some extra overhead, and batching the state updates would lead to improved performance in this case.
let tick = 1000;
//countdown modifying behavior every 1000ms. Active clock
useEffect(() => {
if (time > 0 && counting === true) {
setTimeout(() => {
setTime((state) => state - 1);
console.log(timenow);
}, tick);
}
}, [time, counting]);
//pausing, stopping and resuming
useEffect(() => {
if (time === 0 && counting === true) {
setWorking(!working);
if (working === true) {
playBreak();
unstable_batchedUpdates(() =>{
setTime(breakTarget);
setClockTarget(breakTarget);
}
} else {
playWork();
//update all at once
unstable_batchedUpdates(() =>{
setCombo(combo + 1);
setTime(workTarget);
setClockTarget(workTarget);
}
}
}
}, [time, counting]);
Further improvements to keep the clock in sync
On component mount, grab the current time for start time, and calculate the end time and store both
At every setTimeout in your useEffect, using your stored start time and current time, calculate how much time there is left and set state to that.
//countdown time modifying behavior every 1000ms
const current = new Date()
const [start, setStart] = useState(current)
//this example uses a 25 minute timer as an example
const [end, setEnd] = useState(new Date(start.getTime() + 25 * 60000))
const tick = 1000
useEffect(() => {
if (time > 0 && counting === true) {
setTimeout(() => {
const current = new Date()
const diffTime = current.getDate() - end.getDate()
const timeRemainingInMins = Math.ceil(diffTime / (1000*60));
setTime(timeRemainingInMins);
}, tick);
}
}, [time, counting]);
I may have an idea.
Currently your useEffect hook is set up to run on every render which means every time you change the Time you render the page and rerun the useEffect hook, however you seem to have quite a bit of logic inside the hook, plus react may not render consistently which means that there may be some inconsistencies with your timing.
I suggest using setInterval instead and running the useEffect hook only once at the start to set up the interval using useEffect(() => {...}, []) (notice [] as the second argument)
For the rest of your logic you can always create another useEffect hook that updates when the components change.
With all that in mind your timing logic should look something like this:
useEffect(() => {
interval = setInterval(() => {
setTime(time-1);
console.log(timenow);
}, tick); //runs once every tick
return(() => clearInterval(interval)); //Once the component unmounts run some code to clear the interval
}, [])
Now in another useEffect hook you can watch for changes to time and update the rest of your app accordingly.
This approach should be much more reliable because it doesn't depend on react to update the time.
#The_solution
const component =() => {
const [val, setval]= useState(globalState.get());
useEffect(() => {
setVal(globalState.get());
globalState.subscribe(newVal=> setVal(newVal));
});
return <span>{val}</span>
}
I have a model named 'Deal' which has start_at and end_at attributes. I have implemented a countdown timer using hotwire/stimulus JS.
When the deal starts (start date is in the past, end date is in the future), the countdown timer displaying time left to deal will be shown. e.g Time left to deal: 2 hours, 4 minutes, 30 seconds and so on. It will decrement by 1 second.
If the deal has not yet started (start date is in the future), the page will show "Deal is going to start on #{datetime}".
However, the user needs to refresh the page they are currently on to see a timer if the deal has started in the meantime (i.e. transitioning from "Deal is going to start on #{datetime}" to a countdown timer). I am wondering what's the best way to start the timer without refreshing the page. Thanks.
The way to manage a 'timer' that runs some function every X milliseconds is via the browser's setInterval function.
This function can be used like this - const intervalID = setInterval(myCallback, 500); - where myCallback is the function that will attempt to run every 500ms.
The timer can be 'cancelled' by calling clearInterval and giving it the interval ID that is created as the result of setInterval.
Example HTML
Here we have a basic HTMl structure where we set our controller timer and set the from/to times along with targets that hold the messages based on three states.
These three states are 'before', 'during' (when the current time is between the two times) and 'after'.
<section class="prose m-5">
<div
data-controller="timer"
data-timer-from-value="2022-03-08T10:41:32.111Z"
data-timer-to-value="2022-03-09T11:10:32.111Z"
>
<div style="display: none" data-timer-target="before">
Deal will start on <time data-timer-target="fromTime"></time>
</div>
<div style="display: none" data-timer-target="during">
Deal is active <time data-timer-target="toTimeRelative"></time>
</div>
<div style="display: none" data-timer-target="after">
Deal ended on <time data-timer-target="toTime"></time>
</div>
</div>
</section>
Example Stimulus Controller
This timerController accepts the to and from times as strings (ISO strings are best to use, and remember the nuances of time-zones can be complex).
When the controller connects we do three things; 1. set up a timer to run this.update every X milliseconds and put the timer ID on the class for clearing later as this._timer. 2. Set the time values (the inner time labels for messaging). 3. Run the this.update method the initial time.
this.getTimeData parses the from/to datetime strings and does some basic validation, it also returns these date objects along with a status string which will be one of BEFORE/DURING/AFTER.
this.update - this shows/hides the relevant message parts based on the resolved status.
import { Controller } from '#hotwired/stimulus';
const BEFORE = 'BEFORE';
const DURING = 'DURING';
const AFTER = 'AFTER';
export default class extends Controller {
static values = {
interval: { default: 500, type: Number },
locale: { default: 'en-GB', type: String },
from: String,
to: String,
};
static targets = [
'before',
'during',
'after',
'fromTime',
'toTime',
'toTimeRelative',
];
connect() {
this._timer = setInterval(() => {
this.update();
}, this.intervalValue);
this.setTimeValues();
this.update();
}
getTimeData() {
const from = this.hasFromValue && new Date(this.fromValue);
const to = this.hasToValue && new Date(this.toValue);
if (!from || !to) return;
if (from > to) {
throw new Error('From time must be after to time.');
}
const now = new Date();
const status = (() => {
if (now < from) return BEFORE;
if (now >= from && now <= to) return DURING;
return AFTER;
})();
return { from, to, now, status };
}
setTimeValues() {
const { from, to, now } = this.getTimeData();
const locale = this.localeValue;
const formatter = new Intl.DateTimeFormat(locale, {
dateStyle: 'short',
timeStyle: 'short',
});
this.fromTimeTargets.forEach((element) => {
element.setAttribute('datetime', from);
element.innerText = formatter.format(from);
});
this.toTimeTargets.forEach((element) => {
element.setAttribute('datetime', to);
element.innerText = formatter.format(to);
});
const relativeFormatter = new Intl.RelativeTimeFormat(locale, {
numeric: 'auto',
});
this.toTimeRelativeTargets.forEach((element) => {
element.setAttribute('datetime', to);
element.innerText = relativeFormatter.format(
Math.round((to - now) / 1000),
'seconds'
);
});
}
update() {
const { status } = this.getTimeData();
[
[BEFORE, this.beforeTarget],
[DURING, this.duringTarget],
[AFTER, this.afterTarget],
].forEach(([key, element]) => {
if (key === status) {
element.style.removeProperty('display');
} else {
element.style.setProperty('display', 'none');
}
});
this.setTimeValues();
if (status === AFTER) {
this.stopTimer();
}
}
stopTimer() {
const timer = this._timer;
if (!timer) return;
clearInterval(timer);
}
disconnect() {
// ensure we clean up so the timer is not running if the element gets removed
this.stopTimer();
}
}
I am building a React Native app where my entire back end is provided for by services like Firebase etc.
The app requires clocks on multiple devices to start and end at the same time which can run for up to an hour.
Given a shared starting point in time between devices I have observed drift in the accuracy of setInterval in this 20 seconds of data:
I am attempting to compensate for this deviation in clock timing by measuring it and then compensating for it - here is a code sandbox with my solution.
useTimer hook:
import { useState, useEffect, useRef } from "react";
import moment from "moment";
export const convertMsToMinsAndSecs = (countDown) => {
const seconds = moment
.duration(countDown)
.seconds()
.toString()
.padStart(2, "0");
const minutes = moment
.duration(countDown)
.minutes()
.toString()
.padStart(2, "0");
const minsAndSecs = `${minutes.toString()}:${seconds.toString()}`;
return countDown > 0 ? minsAndSecs : "00:00";
};
const roundTimeStamp = (timeStamp) =>
timeStamp === 0 ? 0 : timeStamp + (1000 - (timeStamp % 1000));
export const useTimer = (
started,
startTime,
length,
resetClock,
clockIntialState
) => {
const initialTimerState = {
start: 0,
end: 0,
timeNow: 0,
remaining: length,
clock: convertMsToMinsAndSecs(length),
internalClockDeviation: 0
};
const [timeData, setTimeData] = useState(initialTimerState);
const intervalId = useRef(null);
const deviation = useRef(null);
useEffect(() => {
setTimeData((prevState) => ({
...prevState,
start: roundTimeStamp(startTime),
end: roundTimeStamp(startTime) + length
}));
if (started) {
intervalId.current = setInterval(() => {
const intervalTime = moment().valueOf();
setTimeData((prevState) => {
return {
...prevState,
timeNow: intervalTime,
remaining: prevState.remaining - 1000,
clock: convertMsToMinsAndSecs(prevState.remaining - 1000),
internalClockDeviation:
prevState.timeNow === 0
? 0
: intervalTime - prevState.timeNow - 1000
};
});
}, 1000 - deviation.current);
}
}, [started]);
useEffect(() => {
deviation.current = timeData.internalClockDeviation;
}, [timeData.internalClockDeviation]);
if (timeData.remaining <= 0 && started) {
resetClock(clockIntialState);
clearTimeout(intervalId.current);
setTimeData(initialTimerState);
}
const compensatedLength = 1000 - deviation.current;
return {
timeData,
intervalId,
compensatedLength,
setTimeData,
initialTimerState
};
};
As I am not running my own server application I would prefer to handle this on the client side if possible. It also means that I do not need to rely on network connections or the availability of a timing server.
Will my approach work across multiple devices, and if so can it be improved, or do I need to build a server side application to effectively handle this? TIA.
When you determine time diff you can not rely on intervals being accurate. Gets worse when tab is in background/not in focus.
Typically you rely on timestamps to get the offset in time, you do not subtract a fix number.
function countDown(totalTime, onComplete, onUpdate, delay = 1000) {
let timer;
const startTime = new Date().getTime();
function next() {
const runningTime = new Date().getTime() - startTime;
let remaining = Math.max(totalTime - runningTime, 0);
onUpdate && onUpdate(remaining);
!remaining && onComplete && onComplete();
var ms = Math.min(delay, remaining);
timer = remaining && window.setTimeout(next, ms);
}
next()
return function () {
timer && window.clearTimeout(timer);
}
}
countDown(5000, function(){ console.log('done1'); }, function(x){ console.log('update1 ', x); });
const out = document.getElementById("out");
const cd = countDown(
60000,
function(){ out.textContent = 'done'; },
function(x){ out.textContent = (x/1000).toFixed(3); },
20
);
document.getElementById("btn").addEventListener('click', cd);
<div id="out"></div>
<button id="btn">stop</button>
This will fail if user changes clock, not much you can do on that. You could ping the server for time, but that also has latency with how long the call takes.
The only way you can synchronize all the clocks together is having all of them observe one single source of truth.
If you create the timestamp on one client. You need to make sure all the other clients are in sync with that data.
What you can do is have a server + client architecture where the server is the single point of truth. But if you are trying to completely synchronize without a single point of truth, you are doomed to fail because of the problems that you can not control like latency of communication between all the client applications in your case clock.
I am doing a time-to-click game as you can imagine the fastest time would be the first place. I just want to have 3 scores. and have it on localstorage but every time I start a new game the actual score resets its value and it doesnt generate other scores. the 3 scores should have as value 0. I tried to push them as arrays but push function is not working well. at this point I am stuck. I dont know what to do. If u may help me, I would be really grateful, thanks!
let times = Array.from({
length: 3
})
let interval2;
// Timer CountUp
const timerCountUp = () => {
let times = 0;
let current = times;
interval2 = setInterval(() => {
times = current++
saveTimes(times)
return times
}, 1000);
}
// Saves the times to localStorage
const saveTimes = (times) => {
localStorage.setItem('times', JSON.stringify(times))
}
// Read existing notes from localStorage
const getSavedNotes = () => {
const timesJSON = localStorage.getItem('times')
try {
return timesJSON ? JSON.parse(timesJSON) : []
} catch (e) {
return []
}
}
//Button which starts the countUp
start.addEventListener('click', () => {
timerCountUp();
})
// Button which stops the countUp
document.querySelector('#start_button').addEventListener('click', (e) => {
console.log('click');
times.push(score = interval2)
getSavedTimes()
if (interval) {
clearInterval(interval);
clearInterval(interval2);
}
})
This:
// Button which stops the countUp
document.querySelector('#start_button').addEventListener('click', (e) => {
Probably should be this:
// Button which stops the countUp
document.querySelector('#stop_button').addEventListener('click', (e) => {
...considering you're already attaching an event-handler to start's 'click' event in the previous statement, and no-one would use #start_button as the id="" of a stop button.