How I can animate this slideshow(by changing its src)? - javascript

Okay okay, so before marking this post as repeated. Let me explain to you:
I made a slideshow in javascript(Vue) and it works by changing its src in an object every time I press a button(next)
It works and all but the problem is that it doesn't get animated no matter what I do, I made a transition on them, set timeout function on it...etc and nothing even the smallest worked.
I could have made another idea which works by the position absolute but I don't want to do that because it will take a loot of time and it will be extremely buggy as position absolute ruins it. So any help on this please?
<template>
<main>
<div id="slideshow">
<figure id="pics">
<img id="slidepic" v-bind:src="pictures[count].src">
<figcaption>{{pictures[count].alt}}</figcaption>
</figure>
</div>
<p>{{count+1}}/{{pictures.length}}</p>
<div id="controls">
<div #click="move(-1)">Previous</div>
<div #click="move(1)">Next</div>
</div>
</main>
Javascript:
methods: {
move: function(num) {
let slideimg = document.querySelector("#slidepic");
slideimg.classList.add("fadeOut");
this.count += num;
if (this.count < 0) {
this.count = this.pictures.length - 1;
} else if (this.count >= this.pictures.length) {
this.count = 0;
}
setTimeout(function() {
slideimg.src = this.pictures[1].src;
}, 1000);
}
}
CSS:
#pics {
opacity: 0.5s;
transition: 0.5s;
}
#pics.fadeOut {
opacity: 1;
}
I didn't include the object(that is in data object, something in Vue) because it would be useless in this situation.

First off all it's transition: <property-name> 0.5s linear; and not transition: 0.5s;. See the transition documentation.
There is no animation for changing the src of an image (see list of animatable css properties).
To do something like this, you can stack all your images into one element and then use css animations and the transform property to create a carousel
var next = document.getElementById('next');
var prev = document.getElementById('prev');
var slideshow = document.getElementById('slideshow');
next.onclick = function() {
var lastChild = slideshow.children[slideshow.children.length - 1];
var firstChild = slideshow.children[0];
var activeEle = document.querySelector('.item.active');
var nextEle = document.querySelector('.item.next');
var prevEle = document.querySelector('.item.prev');
activeEle.classList.remove('active');
activeEle.classList.add('prev');
nextEle.classList.add('active');
nextEle.classList.remove('next');
prevEle.classList.remove('prev');
if (nextEle.nextElementSibling) {
nextEle.nextElementSibling.classList.add('next');
} else {
firstChild.classList.add('next');
}
};
prev.onclick = function() {
var lastChild = slideshow.children[slideshow.children.length - 1];
var activeEle = document.querySelector('.item.active');
var nextEle = document.querySelector('.item.next');
var prevEle = document.querySelector('.item.prev');
// Move the .active class to the previous element
activeEle.classList.remove('active');
activeEle.classList.add('next');
prevEle.classList.add('active');
prevEle.classList.remove('prev');
nextEle.classList.remove('next');
if (prevEle.nextElementSibling) {
prevEle.nextElementSibling.classList.add('prev');
} else {
lastChild.classList.add('prev');
}
};
#slideshow {
width: 100px;
height: 100px;
overflow: hidden;
position: relative;
}
.item {
width: 100%;
height: 100%;
color: white;
background-color: blue;
position: absolute;
/*display: none;*/
top: 0;
left: 0;
z-index: -100;
transition: translateX(-100%);
transition: transform .5s ease-in-out;
opacity: 0;
}
.active {
opacity: 1;
display: block;
z-index: 1;
transform: translateX(0);
}
.next {
transform: translateX(200%);
z-index: 1;
}
.prev {
transform: translateX(-100%);
opacity: 1;
z-index: 1;
}
<div id="slideshow">
<div class="item active">1</div>
<div class="item next">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
<div class="item">6</div>
<div class="item prev">7</div>
</div>
<button type="button" id="prev">Prev</button><button type="button" id="next">Next</button>

As you mention you want to build a slideshow on Vue JS, and because jQuery on top of Vue is not recommended, I suggest that you try Vueper Slides, available on NPM. Unless it is for a learning purpose.

I have created two solutions.
First of all. You've a typo.
#pics {
opacity: 0.5s; // <--- remove "s"
transition: 0.5s; // <--- and forgot the property-name (all, opacity ...)
}
#pics.fadeOut {
opacity: 1;
}
I commented all lines I've changed.
Solution
<template>
<main>
<div id="slideshow">
<!--
I recommend to you ref inestad of querySelector.
https://vuejs.org/v2/api/#ref
I've used the v-bind shorthand.
-->
<figure id="pics1" ref="pics1">
<img id="slidepic" :src="pictures[count].src">
<figcaption>{{pictures[count].alt}}</figcaption>
</figure>
<!--
VueJS build-in transition element.
You have to add a key attribute to detect that the content has changed.
I recommend to use this instead of your solution.
It's easier to implement, no class add/remove struggle, its a part of vue, you can add hooks etc.
https://vuejs.org/v2/guide/transitions.html
-->
<transition tag="figure" name="fade" ref="pics2">
<figure id="pics2" :key="`figure-${count}`">
<img :src="pictures[count].src">
<figcaption>{{pictures[count].alt}}</figcaption>
</figure>
</transition>
</div>
<p>{{count+1}}/{{pictures.length}}</p>
<div id="controls">
<div #click="move(-1)">Previous</div>
<div #click="move(1)">Next</div>
</div>
</main>
</template>
<script>
export default {
name: 'teams',
data() {
return {
count: 0,
pictures: [
{
src: 'https://picsum.photos/200/300',
alt: 'test'
},
{
src: 'https://picsum.photos/200/400',
alt: 'test2'
}
]
};
},
methods: {
// instead of move: function(num) {} you can also write move() {}
move(num) {
this.count += num;
if (this.count < 0) {
this.count = this.pictures.length - 1;
} else if (this.count >= this.pictures.length) {
this.count = 0;
}
}
},
// Watch "count" changes and add or remove classes
// you can also add this to your "move" method
watch: {
count() {
// access the reference
const element = this.$refs.pics1;
element.classList.add('fadeOut');
element.classList.remove('fadeIn');
setTimeout(() => {
element.classList.remove('fadeOut');
element.classList.add('fadeIn');
}, 500); // same duration as css transition
}
}
};
</script>
<style scoped lang="scss">
#pics1 {
opacity: 1;
transition: opacity 0.5s;
}
#pics1.fadeIn {
opacity: 1;
}
#pics1.fadeOut {
opacity: 0;
}
// All classes for <transition>
// There are all automatically used by vue
.fade-enter-active {
transition: opacity 0.5s;
}
.fade-leave {
display: none;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
</style>

Related

Adding time between functions without affecting the typewriter effect

I'm trying to delay a function by using setInterval however it seems to be affecting a typewriting effect I have added to my text. The first <h1> works fine ie the typewriting effect starts from the first character of the sentence whereas the typewriting effect for the next <h2> starts from the 4th word and ignores the previous 3 words - I do believe this has to do with the milliseconds I have set on my setInterval.
var h1MessageArray = ["West Sussex Web Design"];
var h2MessageArray = ["Your down-to-earth website designer."];
var speed = 100;
var textPosition = 0;
typewriter1 = () => {
document.querySelector("#h1Message").innerHTML = h1MessageArray[0].substring(0,
textPosition) + '<span>\u25AE</span>';
if (textPosition++ != h1MessageArray[0].length) {
setTimeout("typewriter1()", speed);
}
}
window.addEventListener('load', typewriter1);
typewriter2 = () => {
document.querySelector("#h2Message").innerHTML = h2MessageArray[0].substring(0,
textPosition) + '<span>\u25AE</span>';
if (textPosition++ != h2MessageArray[0].length) {
setTimeout("typewriter2()", speed);
}
}
window.setInterval(typewriter2, 4000);
#typewriter {
width: fit-content;
margin: auto;
}
span {
animation: blinker 1s linear infinite;
}
#keyframes blinker {
50% {
opacity: 0;
}
}
#media(max-width:480px) {
#typewriter h2 {
font-size: 1rem;
}
}
<div class="container-fluid mt-5">
<div class="row">
<div class="col-12 text-center" id="typewriter">
<h1 id="h1Message"></h1>
<h2 id="h2Message"></h2>
</div>
</div>
</div>
What am I missing?
The problem is you are reusing textPosition in both functions. So the second function starts at the end of the position of the first function.
A simple solution is two different variables.
var h1MessageArray = ["West Sussex Web Design"];
var h2MessageArray = ["Your down-to-earth website designer."];
var speed = 100;
var textPosition1 = 0;
var textPosition2 = 0;
typewriter1 = () => {
document.querySelector("#h1Message").innerHTML = h1MessageArray[0].substring(0,
textPosition1) + '<span>\u25AE</span>';
if (textPosition1++ != h1MessageArray[0].length) {
setTimeout("typewriter1()", speed);
}
}
window.addEventListener('load', typewriter1);
typewriter2 = () => {
document.querySelector("#h2Message").innerHTML = h2MessageArray[0].substring(0,
textPosition2) + '<span>\u25AE</span>';
if (textPosition2++ != h2MessageArray[0].length) {
setTimeout("typewriter2()", speed);
}
}
window.setInterval(typewriter2, 4000);
#typewriter {
width: fit-content;
margin: auto;
}
span {
animation: blinker 1s linear infinite;
}
#keyframes blinker {
50% {
opacity: 0;
}
}
#media(max-width:480px) {
#typewriter h2 {
font-size: 1rem;
}
}
<div class="container-fluid mt-5">
<div class="row">
<div class="col-12 text-center" id="typewriter">
<h1 id="h1Message"></h1>
<h2 id="h2Message"></h2>
</div>
</div>
</div>

Add animation on remove child element

I'm using animation property CSS to animate the window when the user clicks maximize button. The window is created ONLY when the maximize button was clicked and the animation is working, but the problem is when I click the close button the window is doesn't animate. How can I animate when the close button is click?
JavaScript:
Show project image.
_showProjectImage(e) {
const btn = e.target.closest('.projects__maximize-btn');
if (!btn) return;
const { id } = btn.dataset;
const { image } = this._data.find(image => image.id === +id);
this._projectBox = e.target.closest('.projects__box');
const markup = this._generateProjectImage(image);
this._projectBox.insertAdjacentHTML('afterbegin', markup);
}
Hide project image.
_hideProjectImage(e) {
const btnClose = e.target.closest('.btn__close-window');
if (!btnClose) return;
this._projectBox.removeChild(this._projectBox.firstElementChild);
}
HTML Element:
_generateProjectImage(image) {
return `
<div class="projects__window">
<div class="projects__window-content">
<button class="projects__window-close btn__close-window">
<svg class="projects__window-icon">
<use
xlink:href="${icon}#icon-close"
></use>
</svg>
</button>
<div class="projects__window-box">
<img src="${image}" alt="" class="projects__window-image">
</div>
</div>
</div>
`;
}
CSS:
.projects {
&__window {
position: fixed;
inset: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
z-index: 1000;
animation: window-animate 300ms linear;
}
}
#keyframes window-animate {
from {
transform: scale(0);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}

Show div when scrolling using vanilla JavaScript

I'm using vanilla JavaScript and would like to figure out a way where a div container is hidden, but when the user scrolls to 50% of the div container, that's when the div is fully visible. Kind of like a fading-in effect. This is what I have so far:
// delays scroll affects
function debounce(func, wait = 20, immediate = true) {
var timeout;
return function() {
var context = this,
args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}
// adds animation for section cards
function cardActive(e) {
const cards = document.querySelectorAll('.slide-in');
// checks if image is half shown from bottom
cards.forEach(card => {
const slideInAt = (window.scrollY + window.innerHeight) - card.height / 2;
if (slideInAt > card.offsetTop) {
card.classList.add('active')
} else {
card.classList.remove('active');
}
})
};
window.addEventListener('scroll', debounce(cardActive));
.slide-in {
opacity: 50%;
transition: opacity 0.8s;
}
.slide-in.active {
transition: opacity 0.8s;
opacity: 100%;
visibility: visible !important;
}
.placeholder {
margin-top: 400px;
}
.icon {
width: 100px;
}
<div class="placeholder"></div>
<div class="mission-1 slide-in">
<div class="section-card">
<img class="icon" src="https://image.flaticon.com/icons/svg/869/869767.svg" alt="icon">
<h6 class="mission-card-title">Title 1</h6>
<p class="p-special">Lorem Ipsum.</p>
</div>
</div>
<div class="placeholder"></div>
You need to make two small changes:
You need to replace card.height with card.offsetHeight in your JS file.
Use transition in your CSS file, you do not need animation for a fade in effect:
.slide-in {
opacity: 0%;
transition: opacity 0.8s;
}
.active {
transition: opacity 0.8s;
opacity: 100%;
visibility: visible !important;
}
Here the image is already shown with opacity 1 and then once scrolled over it it will fade using just JS.
// adds animation for section cards
window.addEventListener('scroll', (e) => {
last_known_scroll_position = window.scrollY;
let img = document.getElementById("img-1");
if(img.offsetTop < last_known_scroll_position){
img.style.opacity= 0.1;
}else{
img.style.opacity= 1;
}
});
.slide-in {
opacity: 50%;
}
.slide-in.active {
opacity: 100%;
-webkit-animation: animat_show 0.8s;
animation: animat_show 0.8s;
visibility: visible !important;
}
.lorem {
margin-bottom: 500px;
}
img {
width: 500px;
}
<section class="space">
<p class="lorem">lorem ipsum</p>
<div class="slide-in">
<img class="img-1" id="img-1" src="https://image.flaticon.com/icons/svg/869/869767.svg" alt="confetti">
</div>
<p class="lorem">lorem ipsum</p>
</section>

Counter JS issue , doesn't toggle class every 1s

Counter doesn't work properly . I would like to get result when counterFun function toggle .active class and show number in every 1s. For now class is toggled but shows every 2nd number and it doesn't looks like it happen every 1sek
let clickNumber = 0;
const time = 1000;
const h1 = document.querySelector('h1');
function counterFun() {
clickNumber++;
h1.textContent = clickNumber;
h1.classList.toggle('active');
console.log(clickNumber);
}
setInterval(counterFun, time);
.regular {
opacity: 0;
}
.active {
font-size: 100px;
opacity: 1;
transition: .4s;
}
HTML
<div>
<h1 class="regular active">0</h1>
</div>
Your problem is you are using .toggle which doesn't toggle again until the second iteration. So basically you end up with:
0 - toggle
1 - off
2 - toggle
3 - off
Your options are to either use .remove and .add for the class or add a second .toggle.
Example with remove:
let clickNumber = 0;
const time = 1000;
const h1 = document.querySelector('h1');
function counterFun() {
h1.classList.remove('active');
clickNumber++;
h1.textContent = clickNumber;
h1.classList.add('active');
console.log(clickNumber);
}
setInterval(counterFun, time);
.regular {
opacity: 0;
}
.active {
font-size: 100px;
opacity: 1;
transition: .4s;
}
HTML
<div>
<h1 class="regular active">0</h1>
</div>
Example with Toggle:
let clickNumber = 0;
const time = 1000;
const h1 = document.querySelector('h1');
function counterFun() {
h1.classList.toggle('active');
clickNumber++;
h1.textContent = clickNumber;
h1.classList.toggle('active');
console.log(clickNumber);
}
setInterval(counterFun, time);
.regular {
opacity: 0;
}
.active {
font-size: 100px;
opacity: 1;
transition: .4s;
}
HTML
<div>
<h1 class="regular active">0</h1>
</div>
Because you need to remove and add the class at the same time, you can use this code
let clickNumber = 0;
const time = 1000;
const h1 = document.querySelector('h1');
function counterFun() {
$("#demo").addClass("active").delay(800).queue(function(next){
$(this).removeClass("active");
clickNumber++;
h1.textContent = clickNumber;
next();
});
console.log(clickNumber);
}
setInterval(counterFun, time);
.regular {
opacity: 0;
}
.active {
font-size: 100px;
opacity: 1;
transition-delay: .4s;
transition: .4s;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
<h1 class="regular active" id="demo">0</h1>
</div>
PS: you need jquery

requestAnimationFrame is not triggering the function supplied to it

I implemented a infinite loop animation using setInterval. I now like to change the implementation to requestAnimationFrame() so that I will have performance which I am after. For some reasons, requestAnimationFrame() does not call the function supplied to it.
My code looks like this;
var index = 0;
var $btn = $('.btn');
function btnBlinkRun() {
if (index < 2) {
index = index + 1;
} else {
index = 0;
}
$('#ani--scaleinout').removeAttr('id');
$($btn[index]).attr('id', 'ani--scaleinout');
window.requestAnimationFrame(btnBlinkRun);
}
btnBlinkRun();
.btn{
width: 30px;
height: 30px;
background: blue;
border-radius: 100%;
margin-bottom: 10px;
}
#ani--scaleinout {
animation: zoominout 1s ease-in;
}
#keyframes zoominout {
50% {
transform: scale(1.4);
}
100% {
transform: scale(1);
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<div class="btn" id="ani--scaleinout"></div>
<div class="btn"></div>
<div class="btn"></div>
</div>
It looks like what's going on is you are firing requestAnimationFrame multiple times per second. Your css animation has a duration of 1s. But you are removing the attribute every x ms.
It is triggering, it's just happening so fast you can't see it. To demonstrate change your call to window.requestAnimationFrame to use a setTimeout and you'll notice the animation:
setTimeout(function() {
window.requestAnimationFrame(btnBlinkRun);
}, 1000);
Not saying this is a preferred solution, but explaining why this is happening.
It executes alright. But it does not do what you want it to, i presume.
Animation frame fires on every single rending frame (e.g. 60fps) and not on CSS animation keyframes.
The animationend event is your friend here.
var index = 0;
var buttons = document.querySelectorAll('.btn');
function btnBlinkRun() {
if (index < 2) {
index = index + 1;
} else {
index = 0;
}
const element = document.querySelector('#ani--scaleinout');
element.id = null;
buttons[index].id = 'ani--scaleinout';
buttons[index].addEventListener("animationend", btnBlinkRun, { once: true });
}
btnBlinkRun();
.btn{
width: 30px;
height: 30px;
background: blue;
border-radius: 100%;
margin-bottom: 10px;
}
#ani--scaleinout {
animation: zoominout 1s ease-in;
}
#keyframes zoominout {
50% {
transform: scale(1.4);
}
100% {
transform: scale(1);
}
}
<div>
<div class="btn" id="ani--scaleinout"></div>
<div class="btn"></div>
<div class="btn"></div>
</div>

Categories