Speed up setInterval() for one revolution of an infinite carousel (React) - javascript

I'm trying to build an infinite carousel for a webpage. I've managed to get it to work quite well, but there is one small bug which is annoyingly noticeable. Here is the setup:
I have each of the images in an array and I have both an activeIndex and a looping variable in the state. The image array has three clones at the end of it ([...imgArray, imgArray[0], imgArray[1], imgArray[2]]) as 3 images are visible at all times.
I use a useEffect() and setInterval() to update the active index every 2 seconds and if the index is the second from the last item in the array, it sets looping to true.
Then, down in the rendering components, I am dynamically changing the transform: translateX() to move the images and when looping is true it sets transition: none.
The problem is, when the carousel loops back to the beginning, it takes twice as long for that image to move again because it uses two rounds of the setInterval(). I wanted to just make it loop back to the next image, but because there are more animations in there, it doesn't seem possible to me.
My next attempt was to make the setInterval have a variable time, but I can't get that to work either.
Let me know if anybody has any ideas. Please and thank you in advance!
Here is what the code looks like:
// Carousel.tsx
useEffect(() => {
const interval = setInterval(() => {
setActiveIndex(prev => {
setLooping(false)
if (prev + 1 >= carouselArray.length - 1) {
setLooping(true)
return 1;
}
return prev + 1
});
}, 2000)
return () => {
if (interval) clearInterval(interval)
}
}, [carouselArray.length])
return (
<div className={style.carousel}>
<div
className={style.inner}
style={!looping ?
{ transform: `translateX(-${(1 / 3 * 100) * (activeIndex - 1)}%)` } :
{ transition: 'none' }}
>
{carouselArray.map((img, i) =>
<div
className={style.video}
style={
i === activeIndex ? !looping ?
{ margin: '0 0.3rem', opacity: 1, transform: 'scale(100%)' } :
{ transition: 'none' } :
{ opacity: '50%', transform: 'scale(85%)' }}
>
<ImgPreview />
</div>
)}
</div>
</div>
)
// Carousel.module.scss
.carousel {
width: 100%;
overflow-x: hidden;
margin: 0 auto 1rem;
max-width: 800px;
.inner {
display: flex;
transition: transform 0.5s ease;
.video {
flex: 1 0 calc(100% / 3);
transition: all 0.5s ease;
video {
width: 100%;
}
}
}
}

Related

Changing background-image property causes a flicker in Firefox

I'm working on a component that rotates a series of background images in a banner on my page. The problem I'm running into is that when the background-image properties url is changed via state it seems to cause a flash of white. This flashing doesn't seem to happen all the time in Chrome, but does happen consistently in Firefox and sometimes Safari. For additional context I'm using Mac OSX.
At first I assumed this was because the images are being retrieved by the browser when they are requested, but to avoid this I've made some considerations for pre-fetching by rendering a hidden image tag with the resource.
{this.props.items.map(item => (
<img src={item} style={{ display: "none" }} />
))}
I've also tried creating a new image in the rotate method that pre-fetches the next rotation item ahead of the transition, but neither seem to work.
const img = new Image();
img.src = this.props.items[index + 1];
Where am I going wrong here? I've attached an example of the component below. Any help would be appreciated.
class Cats extends React.Component {
constructor(props) {
super(props);
this.state = {
background: props.items[0],
index: 0
};
this.rotate = this.rotate.bind(this);
}
// Let's you see when the component has updated.
componentDidMount() {
this.interval = setInterval(() => this.rotate(), 5000);
}
componentDidUnmount() {
clearInterval(this.interval);
}
rotate() {
const maximum = this.props.items.length - 1;
const index = this.state.index === maximum ? 0 : this.state.index + 1;
this.setState({
background: this.props.items[index],
index
});
}
render() {
return (
<div
className="background"
style={{ backgroundImage: `url(${this.state.background})` }}
>
{this.props.items.map(item => (
<img src={item} style={{ display: "none" }} />
))}
</div>
);
}
}
ReactDOM.render(
<Cats
items={[
"https://preview.redd.it/8lt2w3du0zb31.jpg?width=640&crop=smart&auto=webp&s=58d0eb6771296b3016d85ee1828d1c26833fd022",
"https://preview.redd.it/120qmpjmg1c31.jpg?width=640&crop=smart&auto=webp&s=1b01fc0c3f20098e6bb1f4126c3c2a54b7bc2b8e",
"https://preview.redd.it/guprqpenoxb31.jpg?width=640&crop=smart&auto=webp&s=ace24e96764bb40a01e7d167a88d35298db76a1c",
"https://preview.redd.it/mlzq0x1o0xb31.jpg?width=640&crop=smart&auto=webp&s=b3fd159069f45b6c354de975daffde21f04c3ad5"
]}
/>,
document.querySelector(".wrapper")
);
html, body, .wrapper {
width: 100%;
height: 100%;
}
.background {
position: static;
background-size: cover;
height: 100%;
width: 100%;
transition: background-image 1s ease-in-out;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.1/react-dom.min.js"></script>
<div class="wrapper"></div>
Unfortunately, it seems like this flicker is a known bug in Firefox caused by its image decoder, which won't decode an image until it's displayed for the first time. In the snippet below, I created overlapping divs, one which loads the next image slightly earlier and sits behind the other. This way when the other "flickers," the proper image is already displayed behind, rather than a white background.
You could also theoretically display all the images in the hidden div really quickly, then set it back to white, since the images only need to be displayed once for the decoder to work.
Depending on the long-term goal for this project, the most proper way around this problem may be to use a <canvas> to render your images. The canvas element uses a different decoder which won't cause a flicker.
class Cats extends React.Component {
constructor(props) {
super(props);
this.props.items.forEach((item) => {
const img = new Image(640, 640);
img.src = item;
});
this.state = {
background: props.items[0],
preloadBackground: props.items[1],
index: 0
};
this.rotate = this.rotate.bind(this);
}
// Let's you see when the component has updated.
componentDidMount() {
this.interval = setInterval(() => this.rotate(), 5000);
}
componentDidUnmount() {
clearInterval(this.interval);
}
rotate() {
const maximum = this.props.items.length - 1;
const index = this.state.index === maximum ? 0 : this.state.index + 1;
this.setState({
preloadBackground: this.props.items[index],
index
});
setTimeout(() => {
this.setState({
background: this.props.items[index],
});
}, 100);
}
render() {
return (
<div className="pane">
<div
className="preload-background"
style={{ backgroundImage: `url(${this.state.preloadBackground})` }}
>
</div>
<div
className="background"
style={{ backgroundImage: `url(${this.state.background})` }}
>
</div>
</div>
);
}
}
ReactDOM.render(
<Cats
items={[
"https://preview.redd.it/8lt2w3du0zb31.jpg?width=640&crop=smart&auto=webp&s=58d0eb6771296b3016d85ee1828d1c26833fd022",
"https://preview.redd.it/120qmpjmg1c31.jpg?width=640&crop=smart&auto=webp&s=1b01fc0c3f20098e6bb1f4126c3c2a54b7bc2b8e",
"https://preview.redd.it/guprqpenoxb31.jpg?width=640&crop=smart&auto=webp&s=ace24e96764bb40a01e7d167a88d35298db76a1c",
"https://preview.redd.it/mlzq0x1o0xb31.jpg?width=640&crop=smart&auto=webp&s=b3fd159069f45b6c354de975daffde21f04c3ad5"
]}
/>,
document.querySelector(".wrapper")
);
html, body, .wrapper, .pane {
width: 100%;
height: 100%;
}
.background {
position: static;
background-size: cover;
height: 100%;
width: 100%;
transition: background-image 1s ease-in-out;
}
.preload-background {
position: absolute;
background-size: cover;
height: 100%;
width: 100%;
z-index: -1;
transition: background-image 1s ease-in-out;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.1/react-dom.min.js"></script>
<div class="wrapper"></div>
You can use decode() method that will let you know when image is decoded and ready to be used.
https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/decode
In your case:
const img = new Image();
img.src = this.props.items[index + 1];
img.decode()
.then(() => {
// image is decoded and ready to use
})
.catch((encodingError) => {
// do something with the error.
})

How to reset rotation after onclick execution and make it possible to call it gain

I have a spinning arrows image. On click, it refreshes a captcha. I have it defined in a short css, with a rotation function called on click.
These are the CSS, the HTML and the Javascript:
function rotatearrow() {
arrowreload.style.webkitTransform = 'rotate(360deg)';
arrowreload.style.MozTransform = 'rotate(360deg)';
arrowreload.style.msTransform = 'rotate(360deg)';
}
function reloadcaptcha() {
//does what's needed and plays perfectly
}
.reload {
max-width: 32px;
height: auto;
transform: rotate(0);
transition: all 0.8s;
}
<img id="arrowreload" class="reload" src="../images/reload.png" onclick="rotatearrow(); reloadcaptcha();">
Now the point is: rotatearrow plays at the first click and rotate the arrows, but it never plays after the first time. What am I doing wrong? What changes I have to do to the code?
Every time you click you need to increment the degree:
let degree = 0;
function rotatearrow() {
degree += 360;
arrowreload.style.webkitTransform = `rotate(${degree}deg)`;
}
.reload {
max-width: 32px;
height: auto;
transform: rotate(0);
transition: all 0.8s;
}
<img id="arrowreload" class="reload" src="http://pluspng.com/img-png/triangle-png-triangle-png-clipart-2400.png" onclick="rotatearrow();">
This will work, we just need to keep incrementing the rotation value in multiples i.e -- 360,720,1080.....
function rotatearrow() {
var transformValue = document.getElementById('arrowreload').style.webkitTransform;
var transformInteger = transformValue ? ((transformValue.match(/\d+/g).map(Number)[0]/360)+1) : 1;
document.getElementById('arrowreload').style.webkitTransform = 'rotate('+(360*transformInteger)+'deg)';
}
Hope this is helpful, also this doesn't require external variable declaration...

How to delete image from array if display="none" using Vue.js?

I am using Vue.js in my project. I have a background of images which are animated, they are moving from up to down. Everything connected to the random image, position and etc is inside created():
const vue = require("#/assets/images/vue.png");
const bootstrap = require("#/assets/images/bootstrap.png");
const bulma = require("#/assets/images/bulma.png");
export default {
name: "randImg",
data() {
return {
images: [
vue,
bootstrap,
bulma
],
addedImage: [],
imgTop: -100,
imgLeft: -100,
imgHeight: 64,
imgWidth: 64,
changeInterval: 250
}
},
created() {
const randomImg = func => setInterval(func, this.changeInterval);
randomImg(this.randomImage);
randomImg(this.addImage);
randomImg(this.randomPosition);
},
mounted: function () {
if (this.addedImage[i] = {
style: {display: `none`}
}) {
this.addedImage.remove(this.addedImage[i]);
}
},
methods: {
randomImage() {
const idx = Math.floor(Math.random() * this.images.length);
this.selectedImage = this.images[idx];
},
randomPosition() {
const randomPos = twoSizes => Math.round(Math.random() * twoSizes);
this.imgTop = randomPos(screen.height / 10 - this.imgHeight);
this.imgLeft = randomPos(screen.width - this.imgWidth);
},
addImage() {
if (this.addedImage.length > 500) {
this.addedImage.splice(0, 300);
} else {
this.addedImage.push({
style: {
top: `${this.imgTop}px`,
left: `${this.imgLeft}px`,
height: `${this.imgHeight}px`,
width: `${this.imgWidth}px`
},
src: this.selectedImage
});
}
}
}
}
css
.image {
position: fixed;
z-index: -1;
opacity: 0;
animation-name: animationDrop;
animation-duration: 5s;
animation-timing-function: linear;
animation-iteration-count: 1;
filter: blur(3px);
will-change: transform;
}
#keyframes animationDrop {
15% {
opacity: 0.2;
}
50% {
opacity: 0.4;
}
80% {
opacity: 0.3;
}
100% {
top: 100%;
display: none;
}
}
and html
<div class="randImg">
<img class="image" :style="image.style"
:src="image.src"
v-for="image in addedImage">
</div>
My site is lagging because images are adding to the DOM infinitely. The idea of my animation is that when my image is on keyframe 100% it will have display none. So I decide to simply create an if statement inside mounted() but it doesn't work; I get the error message "ReferenceError: i is not defined".
How can I delete the images when their display become none?
You want each image to persist for five seconds (based on your animation-duration), and you're adding one image every 250ms (based on your changeInterval variable). This means that your image array needs to contain a maximum of twenty images, rather than the 500 you're currently limiting it to.
You could control this by modifying your addImage function to drop the oldest image before adding the newest one. (You're already doing this, except that you're waiting until five hundred of them have built up and then splicing off three hundred at once; better to do it one at a time:)
addImage() {
if (this.addedImage.length > 20) {
this.addedImage.shift() // remove oldest image (the first one in the array)
}
// add a new image to the end of the array:
this.addedImage.push({
style: {
top: `${this.imgTop}px`,
left: `${this.imgLeft}px`,
height: `${this.imgHeight}px`,
width: `${this.imgWidth}px`
},
src: this.selectedImage
});
}
There's no need to read the display value from the DOM, you can just depend on the timing to ensure that the images aren't removed before they're supposed to; modifying the array is all that's necessary to remove the elements from the DOM. (You could harmlessly leave a few extra in the array length, just in case, but there's no need to go all the way to 500.) mounted() wouldn't be useful for this case because that function only runs once, when the component is first drawn onto the page, before there's anything in the addedImages array.
Using v-if to remove images which style is display: none. And i think you can
delete all code in mounted().
<div class="randImg">
<template v-for="image in addedImage">
<img v-if="image.style.display !== 'none'" class="image"
:style="image.style" :src="image.src">
</template>
</div>

how to animate image in react when image change after some interval?

I am trying to make a image slider in react in which a image is change after 5000 second.
I checked from here http://mumbaimirror.indiatimes.com/ .where this website implement that functionality .
I tried to implement same this in react .I am able to make that , but my image is not slide(from right to left) in other words image not showing animation when second image show n view
here is my code
https://codesandbox.io/s/YrO0LvAA
constructor(){
super();
this.pre=this.pre.bind(this);
this.next=this.next.bind(this);
this.state ={
currentSlide :0
}
setInterval(()=>{
var current = this.state.currentSlide;
var next = current + 1;
if (next > this.props.stories.items.length - 1) {
next = 0;
}
this.setState({ currentSlide: next });
}, 5000);
}
One way to do this is to always have the future image (next image) be ready on the right side so you can transition it to the left, at the same time you transition the current one to the left. So they both move together.
In React this would mean that you would need to store the Index of both the current image and your next image, and each X seconds you need to move them both to the left (or the right depending on your action)
Here's a proof of concept:
https://codepen.io/nashio/pen/xLKepZ
const pics = [
'https://cdn.pixabay.com/photo/2017/06/19/07/12/water-lily-2418339__480.jpg',
'https://cdn.pixabay.com/photo/2017/07/18/18/24/dove-2516641__480.jpg',
'https://cdn.pixabay.com/photo/2017/07/14/17/44/frog-2504507__480.jpg',
'https://cdn.pixabay.com/photo/2016/09/04/13/08/bread-1643951__480.jpg',
];
class App extends React.Component {
constructor(props) {
super(props);
const idxStart = 0;
this.state = {
index: idxStart,
next: this.getNextIndex(idxStart),
move: false,
};
}
getNextIndex(idx) {
if (idx >= pics.length - 1) {
return 0;
}
return idx + 1;
}
setIndexes(idx) {
this.setState({
index: idx,
next: this.getNextIndex(idx)
});
}
componentDidMount() {
setInterval(() => {
// on
this.setState({
move: true
});
// off
setTimeout(() => {
this.setState({
move: false
});
this.setIndexes(this.getNextIndex(this.state.index));
}, 500); // same delay as in the css transition here
}, 2000); // next slide delay
}
render() {
const move = this.state.move ? 'move' : '';
if (this.state.move) {
}
return (
<div className="mask">
<div className="pic-wrapper">
<div className={`current pic ${move}`}>
{this.state.index}
<img src={pics[this.state.index]} alt="" />
</div>
<div className={`next pic ${move}`}>
{this.state.next}
<img src={pics[this.state.next]} alt="" />
</div>
</div>
</div>
);
}
}
React.render(<App />, document.getElementById('root'));
// CSS
.pic {
display: inline-block;
width: 100px;
height: 100px;
position: absolute;
img {
width: 100px;
height: 100px;
}
}
.current {
left: 100px;
}
.current.move {
left: 0;
transition: all .5s ease;
}
.next {
left: 200px;
}
.next.move {
left: 100px;
transition: all .5s ease;
}
.pic-wrapper {
background: lightgray;
left: -100px;
position: absolute;
}
.mask {
left: 50px;
overflow: hidden;
width: 100px;
height: 120px;
position: absolute;
}
EDIT: Updated the POC a bit to handle left and right navigation, see full thing HERE
This is by no means an elegant solution, but it does slide the image in from the left when it first appears: https://codesandbox.io/s/xWyEN9Yz
I think that the issue you're having is because you are only rendering the current story, but much of your code seemed to assume that there would be a rolling carousel of stories which you could animate along, like a reel.
With the rolling carousel approach it would be as simple as animating the left CSS property, and adjusting this based on the currently visible story. I.e. maintain some state in your component which is the current index, and then set the 'left' style property of your stories container to a multiple of that index. E.g:
const getLeftForIndex = index => ((index*325) + 'px');
<div className="ImageSlider" style={{ left: getLeftForIndex(currentStory) }}>
<Stories />
</div>

Vanilla Javascript - Animation with different timing

I would like to make an animation using onscroll in Vanilla Javascript. I have 2 classes. The first one is .photography_box which is active and the second one is .photography_box_active which is not active. When i scroll down to 1500px my second class .photography_box_active kicks in and my animation is working great. My .photography_box consist of 12 boxes. When the animation happens all of them come in at the same time which is not what i want. I would like each one of them to come in one after the other. In jQuery i could use $.each but i would like to have the same effect using Vanilla Javascript. Can someone help me solve the problem?
Thanks
CSS code for my classes :
.photography_box {
overflow: hidden;
position:relative;
cursor: pointer;
margin-bottom:20px;
opacity: 0;
cursor: pointer;
-webkit-transform:translateX(-50px);
transform:translateX(-50px);
-webkit-transition: all .3s ease-in-out;
transition: all .3s ease-in-out;
}
.photography_box_active {
opacity: 1;
-webkit-transform: translateX(0px);
transform: translateX(0px);
}
JS code is :
var photoBox = document.getElementsByClassName("photography_box");
window.onscroll = function() {
loopBox()
};
function loopBox(){
if ( window.pageYOffset > 1500 ){
for ( f = 0; f <= photoBox.length -1; f++ ) {
photoBox[f].classList.add("photography_box_active");
};
};
};
You can use setTimeout in your loop to delay the animation for each item based on its index. So if you want a delay of 150ms:
for ( f = 0; f < photoBox.length; f++ ) {
setTimeout(function(){
photoBox[f].classList.add("photography_box_active");
}, 150 * f);
}
Note that as you know in advance the number of items, and as you are using css transitions, this could be done purely in css, keeping your existing js code. Supposing your .photography_box items are in a .box container
.box .photography_box:nth-child(1) { transition-delay: 0 }
.box .photography_box:nth-child(2) { transition-delay: 0.05s }
// and so on...
quite cumbersome to write, especially with vendor-prefixes, but nothing annoying if you are using a css preprocessor like sass
Example: array.forEach(callback[, thisArg])
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach

Categories