Rotating an element also changes scaleX in css animation - javascript

I have five image elements each containing a feather picture. I want to add them to the DOM, where some are randomly flipped right or left, with a random rotation angle.
I want each feather to animate so that they align with the X axis (ie back to 0deg), while also maintaining the scaleX. In my code below, the realignment works but for the flipped feathers, they also twist back the original "un-flipped" appearance during the animation.
How can I prevent this from happening? I know I could achieve this by using a <div> as a wrapper around each feather, but is there a better solution?
Javascript
function feathersPuff() {
for (let i = 0; i < 5; i++) {
let feather = document.createElement("img");
feather.src = "imgs/feather" + i + ".svg";
document.body.appendChild(feather);
let plusOrMinus = Math.random() < 0.5 ? -1 : 1;
feather.style.transform = "scaleX(" + plusOrMinus + ") rotate(" + getRandomInt(50) * plusOrMinus + "deg)"
feather.classList.add("feather");
}
}
CSS
.feather {
display: block;
height: 2%;
position: absolute;
animation: realign;
animation-iteration-count: 1;
animation-direction: linear;
animation-timing-function: ease-in-out;
animation-duration: 500ms;
animation-delay: 0;
animation-fill-mode: forwards;
}
#keyframes realign {
100% { transform: rotate(0deg) }
}

If I understand your question correctly, then a solution might be to define two animations, where one has it's final transform pre-multiplied with a negative scaleX for the negated feather (to ensure that it doesn't produce the undesirable "flip"/twist during the animation):
/*
Define flipped feather animation with positive scale (redundant)
*/
#keyframes regular {
100% {
transform: scaleX(1) rotate(0deg)
}
}
/*
Define flipped feather animation with negated scale. Assuming the
starting transform has scaleX(-1), this animation will stop the
twisting effect from happening
*/
#keyframes flipped {
100% {
transform: scaleX(-1) rotate(0deg)
}
}
With these two animations defined, you can also define corresponding modifier classes to select and apply these randomly to feather elements:
function getRandomInt() {
return Math.random() * 180;
}
function feathersPuff() {
for (let i = 0; i < 10; i++) {
let feather = document.createElement("img");
/* Placeholder image - replace this with your svg */
feather.src = "https://pngriver.com/wp-content/uploads/2017/12/download-free-birds-feather-png-transparent-images-transparent-backgrounds-feather_PNG12958-300x160.png";
document.body.appendChild(feather);
/* Randomly orrientate feather */
if (Math.random() < 0.5) {
/* If regular orrientation, then apply regular modifier class which
will use the "regular" animation (without scaling) */
feather.classList.add("regular");
feather.style.transform =
"rotate(" + getRandomInt(50) + "deg)";
} else {
/* If flipped orrientation, then apply flipped modifier class which
will apply the "flipped" animation (with negated scaling factored in) */
feather.classList.add("flipped");
feather.style.transform =
"scaleX(-1) rotate(" + getRandomInt(50) + "deg)"
}
feather.classList.add("feather");
}
}
feathersPuff();
/* Define flipped feather animation with positive scale (redundant)*/
#keyframes regular {
100% {
transform: scaleX(1) rotate(0deg)
}
}
/* Define flipped feather animation with negated scale */
#keyframes flipped {
100% {
transform: scaleX(-1) rotate(0deg)
}
}
/* Modifier class which animates feathers of the "regular orientation" */
.feather.regular {
animation-name: regular;
}
/* Modifier class which animates feathers of the "flipped orientation" */
.feather.flipped {
animation-name: flipped;
}
.feather {
height: 30px;
display: block;
/* position: absolute; Removed this to prevent feathers overlapping to better
demonstrate the techniques final result */
animation-iteration-count: 1;
animation-direction: linear;
animation-timing-function: ease-in-out;
animation-duration: 2500ms;
animation-fill-mode: forwards;
}

Related

How to apply CSS transform if i had a transform on hover?

I have made a group of elements and set a hover for their class
.cards:hover {
transition: 0.2s;
transform: translate( 0px, -50px);
height: 180px;
width: 120px;
background-size: 120px 180px;
}
There are 10 elements in the class and i have a JS file that onclick singles out the element and i want it to spin so i write the JS and i tell it to add a transform after the click like
document.getElementById(idTag).style.transition = "3s ease";
document.getElementById(idTag).style.transform = "rotate(270deg)";
but it doesn't rotate it. Instead it goes directly to 270 degrees in the shortest possible path. If i remove the transform from the hover then it rotates like normal but if i have a transform on hover it doesn't work. Is there a conflict or something with the hover effect ?
In order to transition vertical displacement (translate) and rotation separately you should probably use a different way of moving the element upwards. Using the top property and a relative position. Then you could have the following css code...
.cards {
position: relative;
transition: top 0.2s, transform 3s ease;
height: 180px;
width: 120px;
background-size: 120px 180px;
top: 0px;
transform: rotate(0deg);
}
.cards:hover {
top: -50px;
}
.cards.rotate {
transform: rotate(270deg);
}
And the following code within your click listener to add the rotate class for rotation after the click.
document.getElementById(idTag).classList.add('rotate')
Now the cards should move up on hover, but rotate on click with separate speeds.
function drawCard() {
var Deck = document.getElementsByClassName("cards");
var idTag = this.id;
document.getElementById(idTag).classList.remove("cards");
document.getElementById(idTag).classList.add("draw-card");
for (i = 0; i < Deck.length; i++) {
Deck[i].style.display = "none";
}
document.getElementById(idTag).style.transition = "2s";
document.getElementById(idTag).style.left = "100px";
document.getElementById(idTag).style.animation = "rotate 2s linear forwards";
on click I separate the picked card by changing the class and then cycle through the rest of the class to make the rest of the cards dissapear. After that I add the rotate animation with the key frames in CSS
#keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

JS/CSS - Moving letters around their original positions on a page

This is my first question on SO, I hope I'm doing it well.
My goal is to have a "magical rune effect", where each letter of a text "floats around" its original position, as if it was suspended in mid-air on a magic parchment of some sort.
To put it simply, it will be used for a game. I know how to make something "float at random" on a page, like a hot air balloon; but this isn't what I'm trying to do : I want the letters to move around their original position.
So far, I've tried something (you can check my fiddle at https://jsfiddle.net/3as4omj2/ ), but I'm running into problems.
(don't worry about the default font and the ugly aqua background, it's used for positionning)
function float(element, range, speed) {
var position = $(element).offset();
$(element).attr( 'original_x', position.left);
$(element).attr( 'original_y', position.top);
$(element).attr( 'range', range );
$(element).attr( 'speed', speed );
drift(element);
}
function drift(element) {
var max = Number.parseInt($(element).attr('range'));
var speed = Number.parseInt($(element).attr('speed'));
var pos_x = Number.parseInt($(element).attr('original_x'));
var pos_y = Number.parseInt($(element).attr('original_y'));
var drift_x = max/2 - Math.floor(Math.random()*max);
var drift_y = max/2 - Math.floor(Math.random()*max);
var final_x = pos_x + drift_x;
var final_y = pos_y + drift_y;
var total_wait = Math.sqrt(drift_x*drift_x+drift_y*drift_y)*speed;
$(element).animate({
left : final_x+"px",
top : final_y+"px"
}, total_wait, /*"linear",*/ function(){
setTimeout(function () {
drift(element);
}, Math.abs(total_wait-Math.floor(Math.random()*150)));
});
}
$( "#go" ).click(function() {
float($("#t"),50, 10);
float($("#e"),50, 10);
float($("#s"),50, 10);
float($("#s"),50, 10);
float($("#t2"),50, 10);
})
Here's my problems and questions so far :
I can't line up my letters to form a word (like, "TEST" is seen vertically, I'd love to see it as an horizontal "TEST"); ideally, using spans, so I can dynamically add a word or remove it without creating dozens of elements.
The text moves bananas... and I don't seem to be understanding why. :(
I'd love to be able to "move the original position" along, so that I can animate the letters further (moving the general text left to right, for example).
Ultimately, is there a way to optimize the size of the font to the user's display ?
Can you guys give me some advice ?
Thank you in advance.
You could try using CSS3 animation instead, setting custom animation delay for every letter using :nth-of-type() selector. To better understand all the animation properties, see this docs. All the rest is the matter of tweaking translate values.
If you are determined enough, creating custom #keyframes for each letter is also an option.
.runes span {
position: relative;
display: inline-block;
padding: 10px;
background: aqua;
animation-duration: 2s;
animation-name: float;
animation-iteration-count: infinite;
animation-direction: alternate;
animation-timing-function: ease-in-out;
}
.runes span:nth-of-type(1) {
animation-delay: .3s;
}
.runes span:nth-of-type(2) {
animation-delay: .4s;
}
.runes span:nth-of-type(3) {
animation-delay: .5s;
}
.runes span:nth-of-type(4) {
animation-delay: .7s;
}
#keyframes float {
0% {
transform: translate(0, 0);
}
25% {
transform: translate(8px, 0);
}
50% {
transform: translate(0, 8px);
}
75% {
transform: translate(5px, 5px);
}
}
<section class="runes">
<span>T</span>
<span>E</span>
<span>S</span>
<span>T</span>
</section>

How to add CSS translation to existing translation?

I am placing a DIV element on the screen using CSS translate. This works fine, except that when the same element is displaced later, the original displacement is discarded.
Set the CSS start position with javascript
div.style.transform ="translate(800px, 400px)";
After setting the starting position randomly with javascript, the CSS animation just resets it back.
CSS Animation
#keyframes testanimation {
0% {
transform: translateY(20px);
}
100% {
transform: translateY(80px);
}
}
How can I combine CSS translations, to take previous displacements into account? In this case, the goal is to have the animation start at randomised locations. The 20px - 80px should be relative.
The best way to do this I would guess is to fetch the previous transform, add something to those values and then apply the new transform.
Another suggestion is to set the position of the element using position, left and top. Using the following code for example:
div.style.position = "absolute";
div.style.left = "800px";
div.style.top = "400px";
That way, the transform would then apply to that position instead of relative to your previous transform.
If it is only about transform, then you need to reset each value in the animation, else it will be overwritten by animation rules.
example:
div {/* translate value reduced to fit in snippet window */
border:solid;
height:40px;
width:40px;
transform:translatex(180px) translatey(140px);
animation:2s testanimation forwards;
}
#keyframes testanimation {
0% {
transform:translatex(180px) translatey(150px)
}
100% {
transform:translatex(180px) translatey(180px)
}
}
<div></div>
Your style to apply to start width should be :
div.style.transform ="translatex(800px) translatey(400px)";
and the animation :
#keyframes testanimation {
0% {
transform:translatex(800px) translatey(420px)
}
100% {
transform:translatex(800px) translatey(480px)
}
}
in order to update only the translatey value
translate or position:relative + coordonates have the same effects/results.
You can also combine them
div {/* value reduced to fit window's demo */
border:solid;
height:40px;
width:40px;
position:relative;
transform:translate(80px,40px);
animation:2s testanimation forwards;
}
#keyframes testanimation {
0% {
top : 20px
}
100% {
top:40px
}
}
<div></div>
the other way round works too :
div {
/* value reduced to fit window's demo */
border: solid;
height: 40px;
width: 40px;
position: relative;
left: 80px;
top: 40px;
animation: 2s testanimation forwards;
}
#keyframes testanimation {
0% {
transform: translatey(20px)
}
100% {
transform: translatey(40px)
}
}
<div></div>
An example of my comment above:
#wrapperDiv {
width: 100px;
height: 100px;
background: green;
transform: translate(400px, 200px) rotate(50deg);
}
#yourDiv {
width: 100px;
height: 100px;
background: red;
animation: testanimation 2s infinite;
}
#keyframes testanimation {
0% {
transform: translateY(20px) rotate(0deg);
}
100% {
transform: translateY(80px) rotate(40deg);
}
}
<div id="wrapperDiv">
<div id="yourDiv">
<div>
<div>
The div#yourDiv will get a transform relative to its parent div#wrapperDiv transform.
I think a more general solution would be to parse the css transform property, as this allows you to keep animating an element without having to worry about its state.
This is the solution I use for this case:
parseTransformMatrix = function(str){
const match_float = "[+\\-]?(?:0|[1-9]\\d*)(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?"
const match_sep = "\\s*,?\\s*"
m = str.match(RegExp(`matrix\\((${match_float}(?:${match_sep}${match_float})*)\\)`))
if(!m || !m.length || m.length < 2) return null
return m[1].match(RegExp(match_float, 'g')).map(x => parseFloat(x))
}
// Test parseTransformMatrix method
console.log(parseTransformMatrix("matrix(1, 0, 0, 1, 96.2351, 309.123)"));
getElementTranslate = function(e){
let t = e.css("transform")
let r = { left: 0, top: 0 }
if(!t) return r
mat = parseTransformMatrix(t)
if(!mat || !mat.length || mat.length != 6) return r
r.left = mat[4]
r.top = mat[5]
return r
}
Here, e is a jQuery element, but you could easily use getPropertyValue instead if you don't want to have this dependency.
Using these functions, you can do something like:
let t = getElementTranslate(e)
e.css({transform:`translate(${t.left+20}px,${t.top+80}px)`})

CSS scroll animation restarts before all text has scrolled

I'm trying to make a list of 100 paragraphs repeatedly scroll up, but the animation is restarting before the list finishes scrolling, at about 48 paragraphs. How can I make sure that all paragraphs scroll before the animation restarts?
div = document.getElementById("titlecontent");
for (c = 0; c < 100; c++) {
str = c;
p = document.createElement("p");
p.innerText = str;
div.appendChild(p);
}
p = document.createElement("p");
p.innerText = "last p reached";
div.appendChild(p);
#titlecontent {
position: absolute;
top: 100%;
-webkit-animation: scroll 10s linear 0s infinite;
-moz-animation: scroll 10s linear 0s infinite;
-ms-animation: scroll 10s linear 0s infinite;
-o-animation: scroll 10s linear 0s infinite;
animation: scroll 10s linear 0s infinite;
}
#-webkit-keyframes scroll {
0% { top: 100%; }
100% { top: -170%; }
}
#-moz-keyframes scroll {
0% { top: 100%; }
100% { top: -170%; }
}
#-ms-keyframes scroll {
0% { top: 100%; }
100% { top: -170%; }
}
#-o-keyframes scroll {
0% { top: 100%; }
100% { top: -170%; }
}
#keyframes scroll {
0% { top: 100%; }
100% { top: -170%; }
}
<div id="titlecontent"></div>
Your problem lies with top/bottom being related to the height of the screen, since the div is longer than those dimensions, it won't work.
I think I found a good solution, using only CSS.
Animating the top/bottom values is impossible, since CSS animations require their exact counterpart to animate, however, there is a property we can use to animate based on the entire height of the element
Introducing: CSS Transforms (translateX).
div = document.getElementById("titlecontent");
for (c = 0; c < 100; c++) {
str = c;
p = document.createElement("p");
p.innerText = str;
div.appendChild(p);
}
p = document.createElement("p");
p.innerText = "last p reached";
div.appendChild(p);
body {
overflow: hidden;
}
body {
overflow: hidden;
}
#titlecontent {
animation: scroll 20s linear 0s infinite;
}
#-webkit-keyframes scroll {
0% { transform: translateY(10%); }
100% { transform: translateY(-100%); }
}
<div id="titlecontent"></div>
The magic happens in these lines:
0% { transform: translateY(10%); }
100% { transform: translateY(-100%); }
Instead of animating offset, we're animating the element's position on the X axis of the screen. Making it -100% of it's actual height, and then animating it to 100% of it's actual height, effectively animating it offscreen before it repeats.
You just need to decide where the scrolling up should start, in this example 10%

Random animation on Simple Image Slideshow

I want to apply a random animation on my slideshow image. First, I tried adding an animation such as scale but it didn't work as I wanted it to.
Things I want to fix:
Smoothness on fadein
Random animation (can be anything at this point, I just want to see how it's done)
Fiddle: http://jsfiddle.net/jzhang172/e7cLtsg9/1/
$(function() {
$('img').hide();
function anim() {
$("#wrap img").first().appendTo('#wrap').fadeOut(3500).addClass('transition').addClass('scaleme');
$("#wrap img").first().fadeIn(3500).removeClass('scaleme');
setTimeout(anim, 3700);
}
anim();
});
body,
html {
margin: 0;
padding: 0;
background: black;
}
#wrap img {
position: absolute;
top: 0;
display: none;
width: 100%;
height: 100%;
}
.transition {
transition: 10s;
}
.scaleme {
transition: 10s;
transform: scale(1.3);
}
.box {
height: 300px;
width: 500px;
position: relative;
overflow: hidden;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="box">
<div id="wrap">
<img src="http://elegantthemes.com/preview/InStyle/wp-content/uploads/2008/11/s-1.jpg" />
<img src="http://elegantthemes.com/preview/InStyle/wp-content/uploads/2008/11/s-5.jpg" />
<img src="http://elegantthemes.com/preview/InStyle/wp-content/uploads/2008/11/s-3.jpg" />
</div>
</div>
Here is a sample using CSS animations and jQuery (for achieving the randomness of animations). If you don't wish to use CSS animations and want to stick to transitions + jQuery effects (like fadeIn), you can still adapt this code to support it because the base idea will still remain the same. I am not too comfortable with jQuery effects and have hence stuck to using CSS animations.
Below is an overview of how it is being done (refer inline comments for more details):
Inside a wrapper there are a group of images that are part of the slide-show (like in your demo).
Using CSS #keyframes, a list of animations (one of which would be used randomly) is created in addition to the default fade-in-out animation. This list is also maintained in an array variable (in JS for picking up a random one from the list).
On load, the default fade-in-out animation and one random animation is added to the 1st element.
An animationend event handler is added to all of the images. This event handler will be triggered when the animation on an element ends. When this is triggered, animation on the current element is removed and the default fade-in-out + a random animation is added to the next element.
The animations are added using inline styles because if we add multiple CSS classes each with one different animation, then the animation in the latest class will override the others (that is, they will not happen together).
A loop effect is achieved by checking if the current element has any other img sibling elements. If there are none, the animation is added back to the 1st element.
$(window).load(function() {
$img = $('img'); // the images
var anim = ['zoom', 'shrink', 'move-down-up', 'move-right-left']; // the list of random animations
var rand = Math.floor(Math.random() * 4) + 1; // random number
$img.each(function() { // attach event handler for each image
$(this).on('animationend', function(e) { // when animation on one image has ended
if (e.originalEvent.animationName == 'fade-in-out') { // check the animation's name
rand = Math.floor(Math.random() * 4) + 1; // get a random number
$(this).css('animation-name', 'none'); // remove animation on current element
if ($(this).next('img').length > 0) // if there is a next sibling
$(this).next('img').css('animation-name', 'fade-in-out, ' + anim[rand - 1]); // add animation on next sibling
else
$img.eq(0).css('animation-name', 'fade-in-out, ' + anim[rand - 1]); // else add animation on first image (loop)
}
});
});
$img.eq(0).css('animation-name', 'fade-in-out, ' + anim[rand - 1]); //add animation to 1st element on load
})
#wrapper {
height: 250px;
width: 300px;
position: relative;
}
img {
position: absolute;
z-index: 1;
bottom: 20px;
left: 10px;
opacity: 0;
transform-origin: left top; /* to be on the safe side */
animation-duration: 3s; /* increase only if you want duration to be longer */
animation-fill-mode: backwards; /* fill mode - better to not change */
animation-iteration-count: 1; /* no. of iterations - don't change */
animation-timing-function: ease; /* better to leave as-is but can be changed */
}
#keyframes fade-in-out {
0%, 100% {
opacity: 0;
}
33.33%, 66.66% { /* duration is 3s, so fade-in at 1s, stay till 2s, fade-out from 2s */
opacity: 1;
}
}
#keyframes zoom {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.5);
}
}
#keyframes shrink {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(.5);
}
}
#keyframes move-down-up {
0%, 100% {
transform: translateY(0px);
}
50% {
transform: translateY(50px);
}
}
#keyframes move-right-left {
0%, 100% {
transform: translateX(0px);
}
50% {
transform: translateX(50px);
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="wrapper">
<img src="https://placehold.it/200/000000/ffffff" />
<img src="https://placehold.it/200/ff0000/ffffff" />
<img src="https://placehold.it/200/00ff00/ffffff" />
<img src="https://placehold.it/200/0000ff/ffffff" />
</div>

Categories

Resources