I have a simple JS script which listens to keyboard input and displays, at a random position, a short animation of every typed letter fading out and getting smaller.
'use strict'
const body = document.querySelector('body')
const ignoreKeys = [
'Alt', 'Shift', 'Control', 'CapsLock', 'Tab', 'Backspace', 'Escape', 'Meta',
'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'
]
document.addEventListener('keydown', function(e) {
if (!ignoreKeys.includes(e.key)) {
// Values 400 & 200 keep the div completely inside the window
const maxHeight = window.innerHeight - 400
const maxWidth = window.innerWidth - 200
const div = document.createElement('div')
div.className = 'anim'
div.textContent = e.key
div.style.top = getRandomInt(0, maxHeight) + 'px'
div.style.left = getRandomInt(0, maxWidth) + 'px'
body.append(div)
setTimeout(function() { div.remove() }, 3000)
}
})
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min) + min)
}
.anim {
position: fixed;
text-align: center;
animation-name: fade;
animation-duration: 2s;
animation-timing-function: ease-in-out;
opacity: 0%;
}
#keyframes fade {
0% { opacity: 100%; font-size: 300px;}
100% { opacity: 0%; font-size: 100px;}
}
By animating the font-size property, each letter gets smaller. However, since its "anchor point" is the top of the div, the visible effect is a letter getting smaller and moving slightly upwards. I would like each letter to shrink towards the vertical center of the div instead.
I can calculate the center the div easily and add the proper top coordinate to the #keyframe property, but I don't know how to modify that property in JS, individually for each div. Is this possible at all via CSS? Or should I rewrite the whole thing in pure JS?
You don't need to adjust the div's top value at all. As there is no border or anything else displayed for the DIV tag itself - just the letter within it - you can adjust either the margin, the border and/or the padding to achieve the same effect as increasing the top value for the DIV. As each of these can be handled within the css transition, you could do something like:
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min) + min)
}
document.addEventListener('keydown', function(e) {
const body = document.querySelector('body')
const ignoreKeys = [
'Alt', 'Shift', 'Control', 'CapsLock', 'Tab', 'Backspace', 'Escape', 'Meta',
'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'
]
if (!ignoreKeys.includes(e.key)) {
// Values 400 & 200 keep the div completely inside the window
const maxHeight = window.innerHeight - 400
const maxWidth = window.innerWidth - 200
const div = document.createElement('div')
div.className = 'anim'
div.textContent = e.key;
div.style.top = getRandomInt(0, maxHeight) + 'px'
div.style.left = getRandomInt(0, maxWidth) + 'px'
body.append(div)
setTimeout(function() { div.remove() }, 3000)
}
})
.anim {
display:block;
position: fixed;
text-align: center;
width:200px;
animation-name: fade;
animation-duration: 2s;
animation-timing-function: ease-in-out;
opacity: 0;
margin:0px;
}
#keyframes fade {
0% { opacity:1; font-size: 300px;}
100% { opacity:0; font-size: 100px; margin-top:100px;}
}
The initial state of the DIV is with margin:0px. Adding a margin-top setting to the keyframes css, increases this from 0 to 100 during the transition. The effect of that is to push the DIV down - and, as noted above, as nothing is being displayed for the DIV itself, the user will not see it move. Note that I have fixed the width of the DIV at 200px so ensure that everything is always centered horizontally - otherwise the DIV width is based on the width of the character, so would change during transition and the character would move to the left as the centre line changes. I've moved some of the code around to make it easier to test - but the only actual change is in the CSS styling. Also note that opacity is a value from 0 to 1, so should not be shown as a percentage.
UPDATE
Have a look at the following snippet. I think that it may be possible to have random font sizes AND random positions using transform rather than animate.
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min) + min)
}
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".split("");
function zoomOUT(){
let d = document.getElementById("test");
d.classList.remove("zoomIN");
d.classList.add("zoomOUT");
}
function zoomIN(){
let d = document.getElementById("test");
let dletter = document.getElementById("testletter");
let t = getRandomInt(20, 60) * 10;
let l = getRandomInt(20, 100) * 10;
let fs = getRandomInt(10, 20) * 10;
dletter.innerHTML = letters[getRandomInt(0, 61)];
d.style.top = t + "px";
d.style.left = l + "px";
d.style.fontSize = fs + "%";
d.classList.remove("zoomOUT");
d.classList.add("zoomIN");
}
#test {
position:absolute;
padding: 50px;
margin: 0 auto;
text-align:center;
vertical-align:middle;
}
.zoomIN {
opacity:1;
transform: scale(3);
transition: transform 2s;
}
.zoomOUT {
opacity:0.5;
transform: scale(0.1);
transition: transform 3s;
}
<button onclick="zoomOUT();" z-index=1>Play</button><button onclick="zoomIN();" z-index=1>Restart</button>
<div id="test" class="zoomIN" style="top:300px; left:300px;" z-index=0><div id="testletter" style="font-size:600%; width:100%; height:100%">A</div></div>
Transform seems to keep things in the same place, so there is no need to adjust any top/margin/border/padding settings at all. In fact, the only things that change are the font-size (using scale(..)) and opacity. The size of the font is determined by the code. Note that this requires the character to be in a div within a div. This is just a test, but should give you enough to convert things into your code requirements.
Related
I am trying to animate elements individually that have the sames classes.
I am a complete jQuery noob, and can't figure out how to achieve that and would please need your help.
Here is the current code I have: (but once I hover on a card/blurb, every single one is animating)
I would like to only target the current element hovered to animate.
Best regards,
Joevin
/*add perspective to row for 3d perspective of child elements*/
.et_pb_blurb {
perspective: 1000px;
}
/*preserve-3d needed for 3d effect on card elements*/
.et_pb_blurb_content {
transform-style: preserve-3d;
transition: all 100ms linear !important;
}
/*transition timing function and duration for card elements*/
.et_pb_main_blurb_image,
.et_pb_module_header,
.et_pb_blurb_description,
.et-mousemove-card .et_pb_button_module_wrapper {
transition: all 750ms ease-out !important;
}
/*transform styles for card elements*/
.et_pb_main_blurb_image.transform-3d {
transform: translateZ(150px) rotateZ(10deg) !important;
}
.et_pb_module_header.transform-3d {
transform: translateZ(150px) !important;
}
.et_pb_blurb_description.transform-3d {
transform: translateZ(50px) !important;
}
.et-mousemove-card .et_pb_button_module_wrapper.transform-3d {
transform: translateZ(150px) rotateX(20deg) !important;
}
</style>
This is my script
//Items
var $hoverContainer = $(".et_pb_blurb");
var $mousemoveCard = $(".et_pb_blurb_content");
var $cardImage = $(".et_pb_main_blurb_image");
var $cardHeading = $(".et_pb_module_header");
var $cardInfo = $(".et_pb_blurb_description");
var $cardButton = $(".et-mousemove-card .et_pb_button_module_wrapper");
//Moving Animation Event
$hoverContainer.on("mousemove", function (e) {
let xAxis = (window.innerWidth / 2 - e.clientX) / 25;
let yAxis = (window.innerHeight / 2 - e.clientY) / 25;
$mousemoveCard.css(
"transform",
`rotateY(${xAxis}deg) rotateX(${yAxis}deg)`
);
});
//Animate on Hover
$hoverContainer.hover(function () {
$mousemoveCard.toggleClass("transform-3d");
$cardHeading.toggleClass("transform-3d");
$cardImage.toggleClass("transform-3d");
$cardButton.toggleClass("transform-3d");
$cardInfo.toggleClass("transform-3d");
});
//Pop Back on mouseleave
$hoverContainer.on("mouseleave", function () {
$mousemoveCard.css("transform", `rotateY(0deg) rotateX(0deg)`);
});
I am trying to make a function which starts to increase (and immediately decrease after opacity equal 1) opacity of div on scroll at the specific place of the window, but my function works only with the first div. I mean second and third divs start changing their opacity at the same time with the first div. But they should do it only if they at the viewport. How to make it works with bottom divs properly?
$(document).ready(function() {
$(window).on('scroll', function() {
var first = $('div.first')
var second = $('div.second')
var third = $('div.third')
var op1 = opacityUp(first, 1, window.innerHeight, 1.5)
var op2 = opacityUp(second, 1, 1.5, 1.5)
var op3 = opacityUp(third, 1, 1.5, 1.5)
})
})
function opacityUp(div, opacityLevel, topMargin, opacitySpeed) {
// div position where to start increasing opacity
const elPosition = (window.pageYOffset - window.innerHeight / topMargin);
div.css({
opacity: function() {
let val = 0 + (elPosition/(window.innerHeight*opacitySpeed));
console.log(elPosition)
if (val <= opacityLevel) {
return val
} else {
return opacityLevel
}
}
});
}
div {
height: 100vh;
opacity: 0;
display: flex;
align-items: center;
justify-content: center;
}
.first {
background: red;
}
.second {
background: green;
}
.third {
background: black;
}
<!-- begin snippet: js hide: false console: true babel: false -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>
<div class="first"></div>
<div class="second"></div>
<div class="third"></div>
I think the problem might have to do with the fact that elPosition doesn't take the actual element's position into account. If you want to define the position where the element begins to reduce opacity as the point at which the div enters the viewport, I'd change it to something like:
const elPosition = (window.pageYOffset - div.offset().top) + window.innerHeight;
Now, elPosition starts negative and reaches 0 when the div enters the viewport at the bottom. Then, elPosition increases by the number of pixels it travels up the screen (as the user scrolls down).
You can set the opacity by doing something like:
let val = (window.innerHeight - elPosition) / window.innerHeight;
if you want the opacity to fade to 0 and finish when the top of the element reaches the top of the viewport. You can add the div's height to the equation to start the opacity fading in the middle or bottom of the div.
I want to make a particle system for my home page. Blue little circular dots should go from left to right and then reapear in the left so that it makes a loop.
Image of particles
The code below will generate 150 dots that all have different properties as speed color and size and will apear at random positions when loading the page. Either by looping the animation or by adding new dots I want to continue the animation infinitely.
// ========== JAVASCRIPT ==========
function Dot(){
var colors = [
"#313146",
"#36364f",
"#3d3d5c",
"#404066"
];
var speed = Math.floor(Math.random() * 20) + 2;
this.obj = document.createElement("div");
this.obj.classList.add("dot");
this.obj.style.top = (window.innerHeight * Math.random()) + 'px'; // random Y-position after page load
this.obj.style.left = (window.innerWidth * Math.random()) + 'px'; // random X-position after page load
this.size = Math.floor(Math.random() * 5) + 4; // random size
this.obj.style.height = this.size + 'px';
this.obj.style.width = this.size + 'px';
this.obj.style.backgroundColor = colors[Math.floor(Math.random()*colors.length)]; // random color
this.obj.style.animation = `move ${speed}s linear`; // start animation
document.body.appendChild(this.obj);
setTimeout(del, speed*1000, this.obj); // THIS FUNCTION SHOULD BE REMOVED IF ANIMATION GETS A LOOP
function del(element) {
element.parentNode.removeChild(element);
};
};
for(var i = 0 ; i < 151 ; i++ ){ // creating 150 dots
new Dot();
};
// ========== CSS ==========
.dot {
border-radius: 50%;
z-index: -1;
}
#keyframes move {
0% {
transform: translateX(0vw);
}
100% {
transform: translateX(100vw);
}
}
My problem is that as the dots apear with random positions, and all of them get a transform: translateX(100vw);, they will move out of the screen for a while before being deleted or reapeared at the beginning. My second image shows in red where the dot is moving to, and where it should move to.
image
What I tried allready:
1.
JS:
this.obj.style.animation = `move ${speed}s linear infinite`; added infinite and deleted the code that deletes the dots.
CSS:
#keyframes move {
0% {
transform: translateX(0vw);
}
100% {
transform: translateX(right);
}
}
<= Does not exist, and couldn't find working code equal to this idea.
This would have been a solution.
2.
Adding a second animation with dots coming from the left when other ones where deleted.
Ended in a gap between the 150 dots of the first animation and the incoming dots of the second animation.
Is there any other possibility of moving the dots from left to right with different properties?
best regards
Since you are setting the position with JS so you can know exactly where each element will appear and use this information to adjust the animation.
Here is a basic example to illustrate:
.dot {
background: blue;
position:fixed;
width: 50px;
height: 50px;
border-radius: 50%;
z-index: -1;
left:var(--x,0px);
animation:move 2s linear infinite;
}
#keyframes move {
0% {
transform: translateX(0vw);
}
100% {
transform: translateX(calc(100vw - var(--x,0px)));
}
}
<div class="dot" style="top:10px;--x:80px;"></div>
<div class="dot" style="top:20px;--x:150px;"></div>
<div class="dot" style="top:100px;--x:350px;"></div>
The variable --x will define left and will get substracted from the 100vw
For better support and since you are using JS, you can get rid of calc() and CSS variables. Simply do a small calculation to find the value of transform.
Here is an example where I am using jQuery for simplicity but you can easily make it a JS-only code:
$('.dot').each(function() {
$(this).css('transform','translateX('+ ($(window).width() - parseInt($(this).css('left')))+'px)');
});
.dot {
background: blue;
position:fixed;
width: 50px;
height: 50px;
border-radius: 50%;
z-index: -1;
animation:move 2s linear infinite;
}
#keyframes move {
0% {
transform: translateX(0px);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="dot" style="top:10px;left:80px;"></div>
<div class="dot" style="top:20px;left:150px;"></div>
<div class="dot" style="top:100px;left:350px;"></div>
Worth to note that you need to update the value on window resize
Another idea to keep the loop effect is to have the same position and the same animation for all and you adjust the delay to simulate the different position:
.dot {
background: blue;
position:fixed;
width: 50px;
height: 50px;
border-radius: 50%;
z-index: -1;
left:0;
animation:move 2s linear infinite;
}
#keyframes move {
0% {
transform: translateX(0px);
}
100% {
transform: translateX(100vw);
}
}
<div class="dot" style="top:10px;animation-delay:-1s;"></div>
<div class="dot" style="top:20px;animation-delay:-0.1s;animation-duration:1s"></div>
<div class="dot" style="top:100px;animation-delay:-0.5s;animation-duration:4s"></div>
The calculation is easy. If the animation duration is D then a delay of -D/2 will place the element in the center intially. -D*0.1 will place the image at 10% and so on.
I would suggest you to use requestAnimationFrame to animate your particles. Take a look at the following example. I've added the move method to the particle which is called from the animation loop and changing the particle's position. It also checks if the particle has reached the end of the screen and resets its position to -10 in this case.
function Dot(){
var colors = [
"yellow",
"red",
"green",
"black"
];
this.x = window.innerWidth * Math.random();
this.speed = Math.floor(Math.random() * 20) + 2;
this.obj = document.createElement("div");
this.obj.classList.add("dot");
this.obj.style.position = "fixed";
this.obj.style.top = (window.innerHeight * Math.random()) + 'px';
this.obj.style.left = this.x + 'px';
this.size = Math.floor(Math.random() * 5) + 4; // random size
this.obj.style.height = this.size + 'px';
this.obj.style.width = this.size + 'px';
this.obj.style.background = colors[Math.floor(Math.random()*colors.length)]; // random color
document.body.appendChild(this.obj);
this.move = function() {
this.x += this.speed;
if (this.x > window.innerWidth) {
this.x = -10;
}
this.obj.style.left = this.x + 'px';
};
};
var dots = Array.apply(null, Array(150)).map(a => new Dot());
requestAnimationFrame(paint);
function paint() {
requestAnimationFrame(paint);
for (dot of dots) {
dot.move();
}
}
.dot {
border-radius: 50%;
z-index: -1;
}
I also recommend you this great book about particle systems. It shows how to implement forces, interaction and complex behavior.
I have a container and and image inside that container. When the container is not in view - the image must be at translate y 0%. When the container is halfway into the viewport and past halfway - the image must be translate y 50%. The % value must be bound to the position of the container into the viewport (controlled by scrolling). The position of the container also determines the speed the image is being translated at. The problem I'm getting is that when the speed value changes, the image jerks up or down instead of changing the speed from that point onward.
Here is my script:
if (wScroll > $(".largeWindow").offset().top - ($(window).height())) {
var cont = $(".largeWindow");
var img = $(".coffee2");
var top = cont.offset().top - cont.height();
var speed;
// translate the image within the container
var moveImage = function() {
setSpeed();
var scroll = Math.floor((wScroll - top)) * speed;
return img.css("transform", "translate(-50%, " + scroll + "%)");
}
// get the position of the image within the container
var getImagePos = function() {
return img.position().top - img.height()/2;
}
var getContainerPos = function() {
var windowHeight = $(window).height();
var contPos = Math.floor(cont.offset().top + cont.height()/2) - wScroll;
return Math.floor(contPos/windowHeight * 100);
}
// set the speed the image will be translated at
var setSpeed = function() {
if (getContainerPos() < 50) {
speed = 0;
}
else if (getContainerPos() < 60 ) {
speed = 0.5
}
else if (getContainerPos() < 70 ) {
speed = 0.5
}
else {
speed = 0.8
}
}
getContainerPos();
moveImage();
getImagePos();
}
Here is the sass for the container and image:
.largeWindow
position: relative
margin: 0 auto
height: 400px
width: 400px
border-radius: 50%
overflow: hidden
background:
image: url(../images/bgTable.jpg)
size: cover
repeat: no-repeat
position: center
attachment: fixed
.coffee2
position: absolute
height: 200px
width: 200px
left: 50%
transform: translateX(-50%)
top: - 200px
background:
image: url(../images/bgCup.png)
size: contain
repeat: no-repeat
position: center
If I have a CSS keyframe animation like this
#keyframes flash-red {
50% {
background: #f00;
}
}
#goflash.anm-flash {
animation-name: flash-red;
animation-duration: .5s;
background: rgba(255, 0, 0, 0);
}
Then I can always trigger the animation like this:
var gf = document.querySelector("#goflash");
gf.classList.remove("anm-flash");
setTimeout(function() {
gf.classList.add("anm-flash");
}, 50);
Is there any way to override the animation-duration/animation-timing-function to be dependent on JavaScript? I'd like to be able to say something like gf.animate("flash-red", "50%") to make the background of gf red, or gf.animate("flash-red", "75%") to make the background more like rgba(255, 0, 0, .5).
Ideally, the same technique would work for transitions. gf.transitionTo("new-class", "50%") would show the element as half way transitioned.
Obviously the flash-red is just an example—I'd like to be able to do this with any animation.
With the built-in animation:
Unfortunately, no
The internals of the transition isn't exposed for JavaScript so you cannot tap into it to set or get the data. And this is for a purpose - if the data were exposed it would mean reduced efficiency as the JavaScript event queue had to be updated. As JS is single-threaded and the animation goes on a separate thread you'll would soon loose the benefit of it running in compiled code internally on a separate thread.
You can however make your own transitions. This involve calculation transitions yourselves.
This is not as complicated as it sounds like as you simply use an interpolation formula for what you want to animate:
current = source + (destination - source) * fraction;
For example, for color you can use it with the color component. Lets assume we have color objects with properties r, g, b:
var color1 = {r: 100, g: 200, b: 55}; //some random color
var color2 = {r: 0, g: 100, b: 100};
var fraction = 0.5; //0-1
Here the current RGB would be:
r = color1.r + (color2.r - color1.r) * fraction;
g = color1.g + (color2.g - color1.g) * fraction;
b = color1.b + (color2.b - color1.b) * fraction;
For positions:
var pos1x = 100;
var pos1y = 100;
var pos2x = 500;
var pos2y = 250;
var fraction = 1; //0-1
posX = pos1x + (pos2x - pos1x) * fraction;
posY = pos1y + (pos2y - pos1y) * fraction;
And so forth.
By making wrapper functions you can easily calculate these and even put them in a loop to animate them.
Example function for setting transition between color 1 and color 2.
Style can be ie. backgroundColor, color etc.:
function setColor(element, style, color1, color2, fraction) {
var r = color1.r + (color2.r - color1.r) * fraction;
var g = color1.g + (color2.g - color1.g) * fraction;
var b = color1.b + (color2.b - color1.b) * fraction;
element.style[style] = 'rgb(' + (r|0) + ',' + (g|0) + ',' + (b|0) + ')';
}
(the r|0 is simply cutting off the decimal part).
And for position, for example:
var pos1 = {x: 0, y: 0};
var pos2 = {x: 200, y: 0};
function setPosition(element, pos1, pos2, fraction) {
var x = pos1.x + (pos2.x - pos1.x) * fraction;
var y = pos1.y + (pos2.y - pos1.y) * fraction;
element.style.left = x + 'px';
element.style.top = y + 'px';
}
A simple demo (use Chrome or Aurora 23 to see sliders, slider comes in next version of FF 23).
Fiddle
Manually set transition at any point between source and destiny, or animate them.
say you have only one animation over your element gf, you can simply control it with animation-delay and animation-play-state:
gf.__proto__.animate = function(percent) {
this.style["animation-play-state"] = "paused";
this.style["animation-delay"] = (
(parseFloat(this.style["animation-duration"] || 1) * -percent) + "s"
);
};
and you can get the computed style as following:
window.getComputedStyle(gf).background
to step through at any speed:
(function animation(time) {
gf.animate( ((time || 0) % desireSpeed ) / desireSpeed );
requestAnimationFrame(animation);
})();
note: this will override animation-delay from css so you'll probably want to keep it in a vairable and add it as an offset in gf.__proto__.animate().
You can't do that as you want it.
Your only posibility is to change play-state after a given delay.
In your case, since the animation lasts 0.5 seconds, to get the animation at 50% you should set a timeout of 0.25 seconds and then set animation-play-state : paused.
Of course that won't be exactly at 50%, don't trust the precision of this method.
editing
Added demo for webkit:
fiddle
The HTML is trivial
<div id="goflash">TEST</div>
<input type="button" value="animate" onclick="animate()">
And the CSS easy
#goflash {
width: 200px;
height: 200px;
left: 35px;
top: 35px;
position: absolute;
background-color: red;
}
.anm-flash {
-webkit-animation-name: flash;
-webkit-animation-duration: 5s;
-webkit-animation-timing-function: linear;
-webkit-animation-iteration-count: 1;
}
#-webkit-keyframes flash {
from { -webkit-transform: rotate(0deg);
background-color: red; }
50% { -webkit-transform: rotate(120deg);
background-color: yellow;}
to { -webkit-transform: rotate(360deg);
background-color: red;
}
}
And the javascript is an extension from what you supplied:
function animate () {
var gf = document.querySelector("#goflash");
gf.classList.remove("anm-flash");
setTimeout(function() {
gf.classList.add("anm-flash");
gf.style.webkitAnimationPlayState = "running";
}, 50);
setTimeout(function() {
gf.style.webkitAnimationPlayState = "paused";
}, 2550);
}
You reset the class, after a small pause start the animation, and a calculated delay after the start, you stop it.
Since the animation time was 5s,and the initial delay 50 ms, the second delay has to be (5000/2) + 50.
Since you have set now the play state to paused, to de able to re-run the animation you have to set the state to running again.
Perhaps using CSS DOM to parse animation's intent (if that's even possible?) and then reconstructing everything in JavaScript.
But that's no mean feat!
I wonder if a CSS preprocessor would help constructing code like this. This is very much all in theory.
Yes,
You can just overide the duration or timing of an animation. Hope I understood what you want to do:
http://jsfiddle.net/SEHyW/
var gf = document.querySelector("#goflash"),
animationDuration = '1s'
gf.classList.remove("anm-flash");
setTimeout(function() {
gf.classList.add("anm-flash");
gf.style["-webkit-animation-duration"] = animationDuration;
}, 1000);