How to animate the text in a for-loop? - javascript

I'm trying to create a sidebar animation effect as:
<div class="sidebar-description sidebar-personal-info-section">
A passionate
<span class="changing-keywords" id="change">
<strong>
<b class="hidden">software engineer</b>
<b class="hidden">lifelong learner</b>
<b class="hidden">blogger</b>
<b class="hidden">traveller</b>
</strong>
</span><br>
</div>
I've written the HTML code but issue is that how should I display each text one at a time with small delay with slide-out animation effect? The loop should work infinite times.

Make it simple by using CSS key-frame animation.
Below is an example.
body{
font-family:calibri;
}
.codinfox-changing-keywords{
vertical-align:top;
overflow:hidden;
height:20px;
position:relative;
display: inline-block;
width: 250px;
}
.hidden{
position:absolute;
top: 20px;
display:inline-block;
width:250px;
opacity:0;
animation: slideme 8s infinite;
}
.hidden:nth-child(3){
animation-delay: 2s;
}
.hidden:nth-child(5){
animation-delay: 4s;
}
.hidden:nth-child(7){
animation-delay: 6s;
}
#keyframes slideme {
0% {
top: 20px;
opacity:0;
}
5% {
top: 0px;
opacity:1;
}
10%{
top : 0;
opacity:1;
}
20%{
opacity:1;
}
25% {
opacity:0.1;
top : 0;
}
30% {
opacity:0;
top: 20px;
}
}
<div class="codinfox-sidebar-description sidebar-personal-info-section">
A passionate
<div class="codinfox-changing-keywords" id="change">
<strong>
<b class="hidden">software engineer</b><br/>
<b class="hidden">lifelong learner</b><br/>
<b class="hidden">blogger</b><br/>
<b class="hidden">traveller</b>
</strong>
</div>
</div>
You can test it here as well!

First, you need to work a little bit on your HTML. The <strong> tag shouldn't be used to style elements, not to mention the <b> tags. Below is my version of the code. First the HTML:
<div class="codinfox-sidebar-description sidebar-personal-info-section">
A passionate
<span class="changing-keyword shown">software engineer</span>
<span class="changing-keyword">lifelong learner</span>
<span class="changing-keyword">blogger</span>
<span class="changing-keyword">traveller</span>
</div>
I took the liberity to change the class names and remove some tags that were, in my opinion, unnecessary here. Now, JavaScript:
const changingKeywords = document.querySelectorAll('span.changing-keyword');
const keywordsToggle = setKeywordsToggle(changingKeywords);
function setKeywordsToggle (keywords) {
let index = 0;
return setInterval(() => {
keywords[index].classList.remove('shown');
if (++index >= keywords.length)
index = 0;
keywords[index].classList.add('shown');
}, 2000);
}
Notice that I actually return the setInterval() function and assign it to keywordsToggle variable. This way, if I ever want to stop the animation, I can easily do so by running clearInterval(). The code toggles through all keywords it finds and assigns the shown class to the element determined by the value of index variable.
Finally, sample CSS:
.sidebar-personal-info-section {
position: relative;
}
.changing-keyword {
font-weight: bold;
opacity: 0;
transition: opacity 1s, visibility 1s;
visibility: collapse;
position: absolute;
padding-left: .2rem;
}
.shown {
opacity: 1;
visibility: visible;
}
Notice the visibility transition, which actually works as transition delay. Other than that, I had to use a position property to make sure that every keyword is positioned near the "passionate" word, otherwise every word would appear in it's original position (I didn't use display:none since it's not animatable).
Working example:
const changingKeywords = document.querySelectorAll('span.changing-keyword');
const keywordsToggle = setKeywordsToggle(changingKeywords);
function setKeywordsToggle (keywords) {
let index = 0;
return setInterval(() => {
keywords[index].classList.remove('shown');
if (++index >= keywords.length)
index = 0;
keywords[index].classList.add('shown');
}, 2000);
}
.sidebar-personal-info-section {
position: relative;
}
.changing-keyword {
font-weight: bold;
opacity: 0;
transition: opacity 1s, visibility 1s;
visibility: collapse;
position: absolute;
padding-left: .2rem;
}
.shown {
opacity: 1;
visibility: visible;
}
<div class="codinfox-sidebar-description sidebar-personal-info-section">
A passionate
<span class="changing-keyword shown">software engineer</span>
<span class="changing-keyword">lifelong learner</span>
<span class="changing-keyword">blogger</span>
<span class="changing-keyword">traveller</span>
</div>

My quick and dirty would look something like this.
// add rotate to Array
Array.prototype.rotate = function(n) {
return this.slice(n, this.length).concat(this.slice(0, n));
}
// setup array
const jobs = ['employee', 'manager'];
// rotate jobs function
const rotateJobs = () => {
document.querySelector('#id').innerHtml = jobs.rotate(1).shift();
}
// set interval
setInterval(rotateJobs, 1000);
The fade in / slide animation should be handled in css transitions.

This something what I finally figured out!
var title = ['software engineer', 'tech blogger', 'traveller', 'lifelong learner'];
var i = 0; // the index of the current item to show
setInterval(function() { // setInterval makes it run repeatedly
document
.getElementById('change')
.innerHTML = title[i++]; // get the item and increment
if (i == title.length) i = 0; // reset to first element if you've reached the end
}, 2000);

Related

Why does text "jump" when the element containing it, is animated with CSS?

const dropdown = document.getElementById('dropdown')
let isDropdownActive = false
function toggleDropdown() {
if(!isDropdownActive) {
isDropdownActive = true
dropdown.style.display = "block"
dropdown.classList.add('animate', 'animate-scale-in')
} else {
dropdown.style.display = "none"
isDropdownActive = false
}
}
html {
font-size: 14px;
}
.absolute {
position: absolute;
}
.animate {
animation-fill-mode: both;
animation-duration: 0.4s;
}
.animate-scale-in {
animation-name: animScaleIn;
}
#keyframes animScaleIn {
from {
transform: scale(0);
}
to {
transform: scale(1);
}
}
<div style="position: relative">
<button
style="padding: 0.625rem 1rem"
onclick="toggleDropdown()"
>
Open Dropdown
</button>
<div
id="dropdown"
class="absolute"
style="background-color: red; display: none"
>
<ul>
<li style="padding: 0.75rem 1rem;">
<span class="text-subtitle text-gray5"> ITEMS </span>
</li>
</ul>
</div>
</div>
As you can see I have a button when clicked a dropdown menu will appear. However at the end of animation inside the menu you can see how the text "ITEMS" jumps to the top a little. If I remove html {font-size: 14px} or set the html font size to 16px there's no stuttering. Also if I remove the padding there's no stuttering too. Please help me with this.
The stuttering in slow-mo:
Since You are using position absolute, You need to add the x axis and y axis
Position: absolute;
top: 100%;
left: 0;
here is the demo:
const dropdown = document.getElementById('dropdown')
function toggleDropdown() {
if(!dropdown.classList.contains('animate')) {
dropdown.classList.add('animate')
} else {
dropdown.classList.remove('animate');
}
}
*{
box-sizong: border-box;
padding: 0;
margin: 0;
}
html {
font-size: 14px;
padding: 20px;
}
[id=dropdown]{
Position: absolute;
top: 100%;
left: 0;
background-color: red;
transform: scale(0);
transform-style: preserve-3d;
will-change: transform;
}
.absolute {
position: absolute;
}
.animate {
animation-fill-mode: both;
animation-duration: 0.4s;
animation-name: animScaleIn;
}
.text-subtitle{display: block}
#keyframes animScaleIn {
to { transform: scale(1); }
}
<div style="position: relative; display: block">
<button style="padding: 0.625rem 1rem"
onclick="toggleDropdown()"
>
Open Dropdown
</button>
<div id="dropdown">
<ul>
<li style="padding: 0.75rem 1rem;">
<span class="text-subtitle text-gray5"> ITEMS </span>
</li>
</ul>
</div>
</div>
The "bump" coincides with the element hitting scale=1 precisely, which leads me to assume that this is something to do with how Chromium optimizes rendering for unscaled elements.
Setting the target scale to be ever-so-slightly-less-than-1 (to prevent that optimization) helps:
const dropdown = document.getElementById('dropdown')
let isDropdownActive = false
function toggleDropdown() {
if(!isDropdownActive) {
isDropdownActive = true
dropdown.style.display = "block"
dropdown.classList.add('animate', 'animate-scale-in')
} else {
dropdown.style.display = "none"
isDropdownActive = false
}
}
html {
font-size: 14px;
}
.absolute {
position: absolute;
}
.animate {
animation-fill-mode: both;
animation-duration: 0.4s;
}
.animate-scale-in {
animation-name: animScaleIn;
}
#keyframes animScaleIn {
from {
transform: scale(0);
}
to {
transform: scale(0.9999999);
}
}
<div style="position: relative">
<button
style="padding: 0.625rem 1rem"
onclick="toggleDropdown()"
>
Open Dropdown
</button>
<div
id="dropdown"
class="absolute"
style="background-color: red; display: none"
>
<ul>
<li style="padding: 0.75rem 1rem;">
<span class="text-subtitle text-gray5"> ITEMS </span>
</li>
</ul>
</div>
</div>
I am afraid you're at the end of the road with your design -- A Web browser, in practice, does not guarantee you pixel perfect rendering. In theory, this might have been the case, since there is no provision in any specification to necessarily produce different glyph sizes for a given font size rule, or padding numbers, for that matter.
In practice, however, Web browsers have, do and will continue to render your hypertext at their own discretion with regard to accuracy achieved, potentially slightly differently from one another, due to a number of variables you have no straightforward (if any) control over.
For instance, testing your code on my Firefox 87 on Windows 10, there is no "jankiness" whatsoever.
You will need to accept the "jankiness" or revise your design to the extent where the problem disappears on its own or where you employ a different UI solution to achieve your goal.
You can look at the details HTML element and see if it may help you solve the problem without all the scripting involved on your part.

setInterval() Method creating strange overlap

As I was playing around trying to build a simple text carousel, I came across an issue I am having a difficult time understanding.
The layout is simple. I have a wrapper and the text I want the carousel to cycle through.
The issue I am having, however, seems as far as I can tell to be coming from the setInterval method. After the animation cycles through all the text and returns to the beginning, there is a strange overlap between the first and second text that is displayed. The first text will animate, but then will return to replace the second text temporarily.
Any help in understanding what is causing this error to render in this way would be greatly appreciated.
let animateSlide = setInterval(moveSlide, 1200);
function moveSlide() {
let carousel = document.getElementById("wordCarousel");
let firstSlide = carousel.children[0];
let createID = document.createAttribute("id");
createID.value = "active";
firstSlide.setAttributeNode(createID);
carousel.appendChild(carousel.firstChild);
carousel.children[carousel.children.length - 1].removeAttribute("id");
}
/* Carousel Styles */
#wordCarousel {
height: 36px;
overflow: hidden;
}
.carouselSlide {
color: #555;
font-size: 36px;
}
#active {
margin-top: 0px;
animation-name: example;
animation-duration: 1.2s;
animation-timing-function: ease;
}
#keyframes example {
from {
margin-top: 0px;
}
to {
margin-top: -40px;
}
}
<div id="wordCarousel">
<div class="carouselSlide">
Item 1
</div>
<div class="carouselSlide">
Item 2
</div>
<div class="carouselSlide">
Item 3
</div>
<div class="carouselSlide">
Item 4
</div>
</div>
Don't rely on setInterval when dealing with CSS animation. You will never have a perfect synchronization. Better consider events like animationiteration/animationend/animationstart
Here is a better idea will less of code an easier to handle.
let carousel = document.querySelector('#wordCarousel div');
carousel.addEventListener('animationiteration', () => {
carousel.appendChild(carousel.children[0]);
});
#wordCarousel {
height: 36px;
overflow: hidden;
}
.carouselSlide {
color: #555;
font-size: 36px;
line-height:100%; /* line-height will make sure the height is equal to 36px, font-size alone isn't enough */
}
#wordCarousel > div {
height:100%;
animation: example 1s infinite;
}
#keyframes example {
to {
transform: translateY(-100%);
}
}
<div id="wordCarousel">
<div>
<div class="carouselSlide">
Item 1
</div>
<div class="carouselSlide">
Item 2
</div>
<div class="carouselSlide">
Item 3
</div>
<div class="carouselSlide">
Item 4
</div>
</div>
</div>

How to show the full name of an acronym on hover in CSS/JS?

Let's say I want my website to display "CSS" and, when hovered, make a sliding animation that results in displaying "Cascading Style Sheets".
I achieved a similar animation using the font size as a workaround (Fiddle) but I would like the words to slide from the initials.
Any idea on how this could be done using the simplest forms of CSS or JS ?
p span {
font-size: 0px;
transition: all 0.3s ease-out;
}
p:hover span {
font-size: 15px;
}
<p>
C<span>ascading</span>
S<span>tyle</span>
S<span>heets</span>
</p>
here is a js solution, it could be a bit better because I reuse the same logic in both functions but that works, feel free to change where the listener listens (in this case document)
const mouseEnterHandle = (event) => {
if (event.target.closest('p')) {
event.target.closest('p').querySelectorAll('span').forEach(el => {
el.style.width = el.scrollWidth + 'px';
})
}
}
const mouseOutHandle = (event) => {
if (event.target.closest('p')) {
event.target.closest('p').querySelectorAll('span').forEach(el => {
el.style.width = 0;
})
}
}
document.addEventListener('mouseover', (event) => mouseEnterHandle(event))
document.addEventListener('mouseout', (event) => mouseOutHandle(event))
document.removeEventListener('mouseover', (event) => mouseEnterHandle(event))
document.removeEventListener('mouseout', (event) => mouseOutHandle(event))
p {
overflow: hidden
display: flex;
}
p:hover span {
opacity: 1;
}
p span {
transition: all 0.3s ease-out;
width: 0;
opacity: 0;
display: inline-flex;
overflow: hidden;
}
<p>
C<span>ascading</span>
S<span>tyle</span>
S<span>heets</span>
</p>
So, after seeing bluebird's answer, I tried to come up with a CSS-only answer.
I achieved the desired result, the only "trick" here is that it is needed to measure the (approximate) width of the words. Here, they respectively are of 56, 24 and 33 pixels wide. I added the length of a space (3.2px) to the first two.
I tried to put
p:hover > span {
width:100%;
}
to avoid this case-by-case solution but it then expands too much.
If anyone has a more (CSS-only) general solution, this answer would be perfectly completed.
p {
display: flex;
}
p:hover > #cas {
width: calc(56px + 3.2px);
}
p:hover > #sty {
width: calc(24px + 3.2px);
}
p:hover > #she {
width: 33px;
}
p span {
width:0px;
overflow:hidden;
transition: all 0.3s ease-in;
transition: all 0.3s ease-out;
}
<!DOCTYPE html>
<html>
<body>
<p>
C<span id="cas">ascading</span>
S<span id="sty">tyle</span>
S<span id="she">heets</span>
</p>
</body>
</html>

List rotation with limited elements

I have div container with list (cards) inside. When I hover it, cards start to moving (translateX animation). container's width is 300px, elements count in container:3, each element width:100px.
So you can see 3 elements in container together overflow:hidden. What I want to make?, is that when there is no element to show translateX animation -100px = 100px blank space after third element, it start from 1 elements in the list immediately after last, with no blank space.
For now, I have no idea how it could be done without duplicates and etc.
Here is what I have at the moment:
Fiddle (Hover cards to see translation animation)
UPD 1:
The code and data (cards count, container size) was taken for example, i'll try to explain better what i want: My goal is to built list of cards and after button was pressed, the list will start moving (like in example with translateX animation) for some time (for example translateX: 12491px, animation-duration: 15s;) and stops. But problem is that amount of crads in the list would be in range of 3-40 cards (each card is 100px width & height). So, when i'll set translateX: 12491px for example, it will be out of range and after the last card in the list would appear blank space. I want first and last card to be tied somehow and after the last card immediately appears first card in the list and etc.. Maybe i am searching for solution in a wrong way, but i guess you understand the main idea.
UPD 2:
I found that cs:go uses animation that i wanted to write on html\css\js. Here is video: youtube.com
html:
<div class="container">
<div class="cards">
<div class="card">
1
</div>
<div class="card">
2
</div>
<div class="card">
3
</div>
</div>
</div>
css:
.container
{
width:300px;
height: 100px;
border: 2px solid black;
overflow: hidden;
}
.card
{
float:left;
height: 100px;
width: 100px;
background-color:blue;
box-sizing: border-box;
border: 2px solid red;
color: white;
font-size: 23px;
}
.cards:hover
{
transform: translateX(-100px);
transition-duration: 3s;
animation-duration: 3s;
animation-fill-mode: forwards;
}
start from 1 elements in the list immediately after last, with no
blank space
This is beyond CSS and you will need Javascript for that. Because, you have tagged the question with Javascript and not jQuery, my answer would be limited to pure Javascript only. Look ma, no JQuery ;)
I have no idea how it could be done without duplicates
Here is a DIY (do it yourself) idea..
The main trick is to show at least one item less than the total you have. If you have 3 cards, show only 2. If you have 4 cards, show only 3. Why, because you need to re-position a card when it goes out of view and wrap it back at the end. If you show exactly the same number of cards that you have, then you cannot break half-a-card and wrap it and you will see some blank space until the first one goes out of view. You get the idea?
Do not use translate or you will end up complicating things for yourself while scripting it out. Keep things simple.
Do not use a wrapper for your cards. Why? Because, we will be re-positioning the cards which have gone out of view. When we do that, the next card will take up its place and immediately go out of view making things further difficult for you.
To keep things simple, arrange your cards with absolute positioning relative to its container. To start with, let all cards stack up at top:0; and left: 0;.
Next wire-up Javascript to position the left property based on the width of each card and arrange them linearly.
Use requestAnimationFrame to control the animation.
Keep track of the left-most card and its left position. When this goes out of view (which is 0 minus width), appendChild this card to its container. This will move the card to the end of cards. Also, change the left property to it based on the last card in the list.
That' all there is to it.
Below is a demo. To make it easy for you to experiment, I have used a settings object to keep the configurable properties which you can easily tweak and see. Look closely at the code and you will find it simple to understand. You can set the iterations settings to 0 to make the animation infinite.
Also, note that you do not need to duplicate or fake the cards. Try the demo and add as many cards you want to.
The inline code comments in the snippet, will further help you understand each line of code and relate to the steps above.
Snippet:
var list = document.querySelector('.cardList'), // cache the container
cards = document.querySelectorAll('.card'), // cache the list of cards
start = document.getElementById('start'), // buttons
stop = document.getElementById('stop'),
reset = document.getElementById('reset'),
raf, init = 0, counter = 0, lastCard, currentIteration = 0, // general purpose variables
settings = { // settings object to help make things configurable
'width': 100, 'height': 100, 'speed': 2,
'iterations': 2, 'count': cards.length
}
;
start.addEventListener('click', startClick); // wire up click event on buttons
stop.addEventListener('click', stopClick);
reset.addEventListener('click', resetClick);
initialize(); // initialize to arrange the cards at start
function initialize() {
// loop thru all cards and set the left property as per width and index position
[].forEach.call(cards, function(elem, idx) {
elem.style.left = (settings.width * idx) + 'px';
});
init = -(settings.width); // initialize the view cutoff
lastCard = cards[settings.count - 1]; // identify the last card
counter = 0; currentIteration = 0; // reset some counters
settings.speed = +(document.getElementById('speed').value);
settings.iterations = +(document.getElementById('iter').value);
}
function startClick() {
initialize(); raf = window.requestAnimationFrame(keyframes); // start animating
}
function stopClick() { window.cancelAnimationFrame(raf); } // stop animating
function resetClick() { // stop animating and re-initialize cards to start again
window.cancelAnimationFrame(raf);
document.getElementById('speed').value = '2';
document.getElementById('iter').value = '2';
initialize();
}
// actual animation function
function keyframes() {
var currentCard, currentLeft = 0, newLeft = 0;
// iterate all cards and decrease the left property based on speed
[].forEach.call(cards, function(elem, idx) {
elem.style.left = (parseInt(elem.style.left) - settings.speed) + 'px';
});
currentCard = cards[counter]; // identify left-most card
currentLeft = parseInt(currentCard.style.left); // get its left position
if (currentLeft <= init) { // check if it has gone out of view
// calculate position of last card
newLeft = parseInt(lastCard.style.left) + settings.width;
list.appendChild(currentCard); // move the card to end of list
currentCard.style.left = newLeft + 'px'; // change left position based on last card
lastCard = currentCard; // set this as the last card for next iteration
counter = (counter + 1) % settings.count; // set the next card index
if ((settings.iterations > 0) && (counter >= (settings.count - 1))) {
currentIteration++; // check settings for repeat iterations
}
}
if (currentIteration >= settings.iterations) { return; } // when to stop
raf = window.requestAnimationFrame(keyframes); // request another animation frame
};
* { box-sizing: border-box; padding: 0; margin: 0; }
.cardList {
position: relative; height: 100px; width: 300px;
margin: 10px; border: 2px solid #33e;
overflow: hidden; white-space: nowrap;
}
.card {
position: absolute; left: 0; top: 0; text-align: center;
height: 100px; width: 100px; line-height: 100px;
background-color: #99e;
font-family: monospace; font-size: 2em; color: #444;
border-left: 1px solid #33e; border-right: 1px solid #33e;
}
div.controls, button { margin: 10px; padding: 8px; font-family: monospace; }
div.controls input { width: 48px; padding: 2px; text-align: center; font-family: monospace; }
<div class="controls">
<label>Speed <input id="speed" type="number" min="1" max="8" value="2" />x</label>
|
<label>Iterations <input id="iter" type="number" min="0" max="8" value="2" /></label>
</div>
<div class="cardList">
<div class="card">1</div>
<div class="card">2</div>
<div class="card">3</div>
<div class="card">4</div>
</div>
<button id="start">Start</button>
<button id="stop">Stop</button>
<button id="reset">Reset</button>
Fiddle: http://jsfiddle.net/abhitalks/1hkw1v0w/
Note: I have left out a few things in the demo. Especially, although width and height of the cards is part of the settings object, but currently it left fixed. You can easily use the settings object to make the dimensions of the cards configurable as well.
Edit:
(as per Op's comment)
If you want a greater control over distance to scroll, duration and timing-functions (easing), then you could implement those yourself using a library. A couple of such good libraries are the Robert Penner's Easing Functions and a jQuery plugin from GSGD. Although you can implement all of that with pure Javascript, it would be easier if you use a library like jQuery.
Catch here is that in order to do so effectively, you must then duplicate the cards. You can do so easily by cloning the entire list a couple of times.
Although you have not tagged this question with jQuery, here is a small demo (using jQuery to get it done quickly) where you can configure the speed and the distance.
Snippet 2:
var $cardList = $('.cardList').first(),
$cards = $('.card'),
$speed = $('input[name=speed]'),
width = 100,
randomize = true,
distance = 20 * width
;
for (var i = 0; i < 50; i++) {
$cards.clone().appendTo($cardList);
}
function spin() {
var newMargin = 0, newDistance = distance,
speed = +($speed.filter(':checked').val());
if (randomize) {
newDistance = Math.floor(Math.random() * $cards.length * 5);
newDistance += $cards.length * 5;
newDistance *= width;
}
newMargin = -(newDistance);
$cards.first().animate({
marginLeft: newMargin
}, speed);
}
$('#spin').click(function() {
$cards.first().css('margin-left', 0);
spin();
return false;
});
* { box-sizing: border-box; padding: 0; margin: 0; }
.cardList {
height: 100px; width: 302px; position: relative;
margin: 10px; border: 1px solid #33e;
overflow: hidden; white-space: nowrap;
}
.card {
display: inline-block; text-align: center;
height: 100px; width: 100px; line-height: 100px;
background-color: #99e;
font-family: monospace; font-size: 2em; color: #444;
border-left: 1px solid #33e; border-right: 1px solid #33e;
}
.cardList::before, .cardList::after {
content: ''; display: block; z-index: 100;
width: 0px; height: 0px; transform: translateX(-50%);
border-left: 8px solid transparent;
border-right: 8px solid transparent;
}
.cardList::before {
position: absolute; top: 0px; left: 50%;
border-top: 12px solid #33e;
}
.cardList::after {
position: absolute; bottom: 0px; left: 50%;
border-bottom: 12px solid #33e;
}
div.controls, button { margin: 10px; padding: 8px; font-family: monospace; }
div.controls input { width: 48px; padding: 2px; text-align: center; font-family: monospace; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="controls">
<label>Speed: </label>
|
<label><input name="speed" type="radio" value='6000' />Slow</label>
<label><input name="speed" type="radio" value='5000' checked />Medium</label>
<label><input name="speed" type="radio" value='3000' />Fast</label>
</div>
<div class="cardList"><!--
--><div class="card">1</div><!--
--><div class="card">2</div><!--
--><div class="card">3</div><!--
--><div class="card">4</div><!--
--></div>
<button id="spin">Spin</button>
Fiddle 2: http://jsfiddle.net/abhitalks/c50upco5/
If you don't want to modify the dom elements you could take advantage of flex-item's order property;
to do this you'd still need a little JS to add this property after animation has ended;
I also changed to animation instead of transition so it automatically resets the transform property at the end of animation.
$('.cards').mouseenter(function() {
setTimeout(function() {
$('.card').first().css("order", "2");
}, 3000);
});
$('.cards').mouseleave(function() {
$('.card').first().css("order", "-1");
});
.container {
width: 300px;
height: 100px;
border: 2px solid black;
overflow: hidden;
}
.card {
float: left;
/* height: 100px;
width: 100px;*/
background-color: blue;
box-sizing: border-box;
border: 2px solid red;
color: white;
font-size: 23px;
flex: 0 0 25%;
}
.cards:hover {
animation: trans 3s;
}
/**/
.cards {
width: 400px;
height: 100%;
display: flex;
transition: transform 3s;
}
#keyframes trans {
0% {
transform: translateX(0)
}
100% {
transform: translateX(-100px)
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<div class="container">
<div class="cards">
<div class="card">1</div>
<div class="card">2</div>
<div class="card">3</div>
</div>
</div>
fiddle
But if you're OK to use JS I suggest you manipulate the order of DOM elements directly,taking the first child element of .cards and appending it to the end of list at the end of each animation;
try this:
var anim;
$('.cards').mouseenter(function(){
anim = setInterval(function(){
$('.cards').append($('.card').first())
},3000)
});
$('.cards').mouseleave(function(){
clearInterval(anim)
});
.container{
width:300px;
height: 100px;
border: 2px solid black;
overflow: hidden;
}
.card{
float:left;
/* height: 100px;
width: 100px;*/
background-color:blue;
box-sizing: border-box;
border: 2px solid red;
color: white;
font-size: 23px;
/**/
flex:0 0 25%;
}
.cards:hover{
animation: trans 3s infinite;
}
/**/
.cards{
width:400px;
height:100%;
display:flex;
}
#keyframes trans {
0% {
transform: translateX(0)
}
100% {
transform: translateX(-100px)
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<div class="container">
<div class="cards">
<div class="card">
1
</div>
<div class="card">
2
</div>
<div class="card">
3
</div>
</div>
</div>
in case you want one card to be present at same time both at the beginning and at the end of card-list you'll need to make a deep-copy / clone of the element;
here's an example;
Update 2:
I wrote a jquery plugin that may act the way you want:
you can add as many cards as you want, right now the "translateX" is random (the script will choose randomly the final card)
link to the demo
Update:
I know, I used duplicates, but now my code works on three cards:
I added three "fake" cards
Each "real" card has it's own animation
the "fake" cards will be overlapped by the real ones once their cycle is finished ("when there is no element to show" as you asked)
check the snippet:
.container {
width: 300px;
height: 100px;
border: 2px solid black;
overflow: hidden;
}
.card {
float: left;
height: 100px;
width: 100px;
background-color: blue;
box-sizing: border-box;
border: 2px solid red;
color: white;
font-size: 23px;
}
.cards {
width: 600px;
}
.container:hover .card1{
animation: 1600ms slide1 infinite linear;
}
.container:hover .card2{
animation: 1600ms slide2 infinite linear;
}
.container:hover .card3{
animation: 1600ms slide3 infinite linear;
}
.fakecard{z-index:-1000;}
.container:hover .fakecard{
animation: 1600ms fakeslide infinite linear;
}
#keyframes slide1 {
0% { transform: translateX(0px); }
33% { transform: translateX(-100px); }
33.1% { transform: translateX(+200px); }
100% { transform: translateX(0px); }
}
#keyframes slide2 {
0% { transform: translateX(0px); }
66% { transform: translateX(-200px); }
66.1% { transform: translateX(100px); }
100% { transform: translateX(0px); }
}
#keyframes slide3 {
0% { transform: translateX(0px); }
99% { transform: translateX(-300px); }
99.1% { transform: translateX(+300px); }
100% { transform: translateX(0px); }
}
#keyframes fakeslide {
0% { transform: translateX(0px); }
99% { transform: translateX(-300px); }
99.1% { transform: translateX(+300px); }
100% { transform: translateX(0px); }
}
<div class="container">
<div class="cards">
<div class="card card1">
1
</div>
<div class="card card2">
2
</div>
<div class="card card3">
3
</div>
<div class="card fakecard">
1 (fake)
</div>
<div class="card fakecard">
2 (fake)
</div>
<div class="card fakecard">
3 (fake)
</div>
</div>
</div>
Previous answer:
Is this what you are trying to achieve?
I don't think you can do it without duplicates...
If not, can you explain better what you are trying to achieve here?
[snipped code removed]
Here is the same effect that you mentioned, with a little tweak on your CSS and a helpful hand from jQuery.
CSS
Change your selector for the translateX animation to apply on each of the .card boxes when their immediate parent is hovered, and not the .cards (which is the immediate parent of the .cards). This is because you'd want the cards to move to the left, and not the window through which they appear while making the movement.
That is,
.cards:hover .card {
transform: translateX(-100px);
transition-duration: 1.5s;
animation-duration: 1.5s;
animation-fill-mode: forwards;
}
jQuery
var $container = $('.container');
var cardWidth = 100;
$container.on('mouseenter', function (e) {
e.preventDefault();
var $card0Clone = $('.card').eq(0).clone(); // clone of the first .card element
$('.cards').append($card0Clone);
updateWidth();
});
$container.on('mouseleave', function (e) {
e.preventDefault();
var $cards = $('.card');
$cards.eq(0).remove(); // remove the last .card element
});
function updateWidth() {
$('.cards').width(($('.card').length) * cardWidth); // no of cards in the queue times the width of each card would result in a container fit enough for all of them
}
Code Explained
As you move in the mouse pointer, a clone of the first card is created, and appended to the end of the cards collection. Further, as you move the mouse out of the hover area, the original .card (which was cloned earlier) will be removed from the head of the queue - hence, producing a cyclic effect.
The real trick though is with the updateWidth function. Every time the mouse enters the .container the width of the .cards' immediate parent (i.e. .cards div) is updated, so that .cards div is wide enough to fit in all the .cards, and therefore, making sure that each of the cards push against each other and stay in one line at the time the translation animation is being done.
Here is a simple technique that manipulates the Dom to create your desired effect
Javascript:
document.querySelector('.cards').addEventListener('mousedown', function(e) {
if (e.clientX < (this.offsetWidth >> 1)) {
this.appendChild(this.removeChild(this.firstElementChild));
} else {
this.insertBefore(this.lastElementChild, this.firstElementChild);
}});
then in you css use the nth-of-type selector to position elements as required.
Here is your fiddle
If you are using mouseover you might need to wait for transitionend event before firing again.
Check out this demo
Here I used JQuery, you can configure your animation using two variables
var translateX = 1000; //adjust the whole distance to translate
var stepSpeed = 100; //adjust the speed of each step transition in milliseconds
After setting your variables, on the click event of the cards do the following:-
Get the number of the steps required based on translateX
Loop for the number of steps
Inside each loop (each step) move the cards 1 step to the left, then put the first card to the end of the cards to form the connected loop, then return back the cards to it's initial position
Here is the code:
var stepsNumber = translateX/100;
for(var i=0; i< stepsNumber; i++)
{
$('.cards').animate({'left' : -100}, stepSpeed,function(){
$('.cards div:last').after($('.cards div:first'));
$('.cards').css({'left' : '0px'});
});
}

Is it possible to loop changing opacity values in HTML5 or css?

This is the code I'm currently working with. It works to my purposes of layering the two images. What I am trying to do is have the layer0 opacity lower to 0 as the layer1 opacity increases to 100 over a few seconds. {and then on to layer1 with layer2 and so on eventually looping back to layer0}
Any help would be appreciated.
<head>
<style>
div.layer0
{
width: 371px;
height: 345px;
background:url(image2.jpg);
opacity:1;
filter:alpha(opacity=100); /* For IE8 and earlier */
}
div.layer1
{
width: 371px;
height: 345px;
background:url(image3.jpg);
opacity:0;
filter:alpha(opacity=0); /* For IE8 and earlier */
}
</style>
</head>
<body>
<div class="layer0">
<div class="layer1">
</div>
</div>
</body>
To continually do this in a loop, you'll need some javascript to add an appropriate active class to the image you want displayed. Then using CSS transitions you can achieve the fading between images that you require.
I created a jsfiddle to give you an example of this working: http://jsfiddle.net/pacso/H6dqq/
The basics are as follows.
Some simple HTML divs which you'll be fading:
<div class='red square active'></div>
<div class='yellow square'></div>
<div class='green square'></div>
<div class='blue square'></div>
These are just going to be coloured squares, but yours could contain images.
Next, some CSS markup:
.red {
background-color: red;
}
.blue {
background-color: blue;
}
.green {
background-color: green;
}
.yellow {
background-color: yellow;
}
.square {
width: 200px;
height: 200px;
position: absolute;
top: 20px;
left: 20px;
opacity: 0;
transition: opacity 2s;
-webkit-transition: opacity 2s; /* Safari */
}
.active {
opacity: 1;
}
Note that my transition will alter the opacity of the div itself. You may need to change this as needed.
Now the javascript to make it work on an endless loop:
jQuery(function() {
window.setInterval(function () {
activeSquare = $('.active');
nextSquare = activeSquare.next()
if (nextSquare.length == 0) {
nextSquare = activeSquare.siblings().first();
}
nextSquare.addClass('active');
activeSquare.removeClass('active');
}, 3000);
});
Fairly straightforward. Click the link to my fiddle and hit the run button if you want to see a working demo.
Short answer: not easily.
You're probably better off with javascript for the looping. You could make a delayed keyframe animation, but that won't allow you to loop from the start again: jsfiddle.net/G4PTM (firefox/ie10) -- You could make a lot of keyframes with different timings and you can make it work, but it would require quite a bit of code and not scale well (say you wanted to add another layer/image the code would quickly become unmanagable)
With some javascript, you can just loop through the divs and add and remove a classname to trigger the transitions, like Jon mentioned. Here is a working demo (using jQuery for simplicity, let me know if you need vanilla js)
html
<div class="layer0">
</div>
<div class="layer1">
</div>
<div class="layer2">
</div>
css
div {
width: 371px;
height: 345px;
opacity: 0;
position: absolute;
transition: opacity 2s;
}
div.active {
opacity: 1;
}
div.layer0 {
background:url(http://lorempixel.com/373/345);
}
div.layer1 {
background:url(http://lorempixel.com/372/345);
}
div.layer2 {
background:url(http://lorempixel.com/374/345);
}
js+jquery
var firstDiv = $(".layer0");
var current;
function loopsie() {
// if first iteration or reached end, use first div
if (!current || !current.length) current = firstDiv;
current.addClass("active");
setTimeout(function() {
current.removeClass("active");
setTimeout(function() {
current = current.next();
loopsie(); // recurse
}, 2000);
}, 2000);
}
//initialize
loopsie();
Working demo at http://jsfiddle.net/G4PTM/2/
Plain JavaScript (Without jQuery):
var firstDiv = document.querySelector(".layer0"); // IE 8+
var current;
function loopsie() {
// if first iteration, use first div
if (!current) current = firstDiv;
current.classList.add("active"); // IE 10+, shim at https://developer.mozilla.org/en-US/docs/Web/API/Element.classList
setTimeout(function() {
current.classList.remove("active");
// account for text node (if there is whitespace in html)
if (current.nextSibling && current.nextSibling.nodeName == "DIV") {
current = current.nextSibling;
} else if (current.nextSibling && current.nextSibling.nextSibling && current.nextSibling.nextSibling.nodeName == "DIV") {
current = current.nextSibling.nextSibling;
} else {
// reached end
current = firstDiv;
}
loopsie(); // recurse
}, 2000);
}
//initialize
loopsie();
http://jsfiddle.net/G4PTM/6/
You can use CSS transitions. The example below fades .layer0 in and out in a timespan of 500 ms:
div.layer0 {
opacity: 1;
-webkit-transition:opacity 500ms ease-out;
-moz-transition:opacity 500ms ease-out;
-o-transition:opacity 500ms ease-out;
transition:opacity 500ms ease-out;
}
div.layer0:hover {
opacity: 0;
}

Categories