I want to create an animation for an element with following properties:
it animates the element when it enters the viewport
if the element left the viewport and then enters it again it should be animated again
depending on the scroll direction/intersected side (from top or bottom) the animation should be different
For this purpose I use an IntersectionObserver and I came close to the desired outcome.
The only problem I am facing is, when I translate the element in the scroll direction (which is in this case transform: translateY) during the animation. This will cause the IntersectionObserver to trigger multiple or even infinite times.
function isIntersectingFromTop(entry){
return entry.boundingClientRect.bottom != entry.intersectionRect.bottom;
}
function isIntersectingFromBottom(entry){
return entry.boundingClientRect.top != entry.intersectionRect.top;
}
var count = 0;
function incrementCounter(entry){
document.querySelector(".counter").textContent += "intersectionRation (" + count + "): " + entry.intersectionRatio + "\n";
count++;
}
let observer = new IntersectionObserver(
function (entries, observer) {
entries.forEach(function(entry){
incrementCounter(entry)
if (entry.isIntersecting) {
if(isIntersectingFromTop(entry)){
entry.target.classList.add("animated--up-in");
} else if(isIntersectingFromBottom(entry)) {
entry.target.classList.add("animated--down-in")
}
} else {
/** element is not in viewport anymore
* this will be triggered right after the animation starts
* since the translate is moving the elment out of the view
* which is causing a new intersection (isIntersecting = false)
*/
entry.target.classList.remove("animated--up-in");
entry.target.classList.remove("animated--down-in");
}
});
});
observer.observe(document.querySelector(".to-animate"));
.container {
height: 1000px;
width: 100%;
}
.box{
position: static;
width: 100px;
height: 100px;
background: red;
margin-top: 10px;
}
.to-animate{
background: blue;
opacity: 0;
}
.animated--up-in {
animation: animateUpIn 1.5s forwards ease;
}
.animated--down-in {
animation: animateDownIn 1.5s forwards ease;
}
#keyframes animateUpIn {
from {
transform: translateY(100px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
#keyframes animateDownIn {
from {
transform: translateY(-100px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.counter {
position: fixed;
top: 10%;
left: 30%;
color: black;
height: 80%;
width: 50%;
overflow-y: auto;
padding: 10px;
}
<div class="container">
<pre class="counter"></pre>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box to-animate"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
</div>
Question
How can I "tell" the IntersectionObserver to ignore translated position and just use the initial/original position to calculate intersections (with no extra element)? Is this even possible?
I think that in this case you need to track the position on the screen of the stationary containers in which the animated elements will be nested.
I have a similar problem with translateX on mobile device
Animation has translateX(-100% or 100%);
threshold: 0.1
Block has width 100%, but it should work with a visibility of 10% (threshold 0.1), therefore it cannot work, the animation switches back and forth, endlessly.
If it's 90%, then it will work
Related
I am trying to create a slider component that slides text from right to left, and if the length of the text is longer than the width of the div element keep moving the text until the end of the text is reached.
The code I have just slides until the beginning of the text hits the left end of the div element, and I am trying to have it move until the end of the string hits the right side of the div. The slider component will have both strings which are shorter than the width, in which case the text should move until the left side of the div, if it is longer than the width it should move the end of the string hits the right side of the div.
Any pointers or suggestions on how I can move this text until the end of the text hits the right side of the div?
code:
.slider {
width: 100%;
overflow: hidden;
margin-left: 300px;
background-color: aqua;
}
.slider h2 {
animation: slide-left 10s;
}
#keyframes slide-left {
from {
margin-left: 100%;
}
to {
margin-left: 0%;
}
}
#keyframes slide-right {
to {
transform:translateX(0);
}
}
<div className="slider">
<h2>Hello world, I need a very long text to show how the text will move</h2>
</div>
UPDATE
Using a timer I managed to have an animation that stops when a condition is hit. I sense that the animation speeds-up at the end, so I will try a bit further.
.slider {
width: 100%;
overflow: hidden;
margin-left: 300px;
background-color: aqua;
}
.slider h2 {
animation: slide-left 30s;
animation-play-state: paused;
}
#keyframes slide-left {
from {
margin-left: 0%;
}
to {
margin-left: -1000%;
}
}
#keyframes slide-right {
to {
transform:translateX(0);
}
}
useEffect(() => {
if(executeTimer){
let interval = setInterval(() => {
let sliderElement = document.getElementById('slider');
let sliderWidth = sliderElement.clientWidth;
setCounter(counter + 1);
if(counter === 100){
document.getElementById("slider").style.animationPlayState = "running";
}
if(sliderWidth >= sliderElement.scrollWidth) {
setExecuteTimer(false);
document.getElementById("slider").style.animationPlayState = "paused";
}
},10);
return () => clearInterval(interval);
}
}, [executeTimer, counter]);
return (
<div className="slider" id="slider-container">
<h2 id="slider">Hello world, this is just a little bit longer; no it needs to be just a little bit longer</h2>
</div>
);
I have a similar need so here's the solution I've came up with.
transform: translateX(calc(-100% + 200px));
This did the trick for me.
Only issue is that the speed of the scrolling depends on the length of the text. It doesn't bother me so I won't be trying to 'fix' it.
:root {
--slider-width: 400px;
}
body {
background-color: gray;
}
.slider {
width: var(--slider-width);
height: 40px;
overflow: hidden;
background-color: blue;
white-space: nowrap;
}
.text {
font-size:26px;
display: inline-block;
animation: slide 20s linear infinite;
}
#keyframes slide {
0% {
transform: translateX(0%);
}
50% {
transform: translateX(calc(-100% + var(--slider-width)));
}
100% {
transform: translateX(0%);
}
}
<div class="slider">
<div class="text">
Hello world, I need a very long text to show how the text will move
</div>
</div>
I hope this will help you.
You could change the
margin-left: 0%;
to
margin-left: -200%;
Or some other value which at the very least is -100% or below. If the text may be 3x as wide as the screen then -300% would be needed.
I don't understand why there's having a problem with .toggle() when toggling class names with animations. Consider this experiment I made:
var query = document.querySelector.bind(document);
query('button').addEventListener('click', function() {
[].forEach.call(query('.container').children, function(box, i) {
setInterval(function() {
box.classList.toggle('popIn');
}, 300 * i);
})
})
.container > .box {
width: 100px;
height: 100px;
background: cyan;
display: inline-block;
transform: scale(0);
transition: all 0.3s ease-in-out;
}
.container > .box.popIn {
transform: scale(1);
}
<button>Click</button>
<div class="container">
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
</div>
When I click the button, it toggles the class names of boxes indefinitely as if it's not sure if it adds or removes them. Is it because the .toggle() method is inside a forEach loop?
You're issue is in the setInterval function. You are saying, perform this event every 300ms. What you want is setTimeout, which is telling the event to stop after 300ms. See the below snippet where I have made the change.
See more information on setTimeout
See more information on setInterval
var query = document.querySelector.bind(document);
query('button').addEventListener('click', function() {
[].forEach.call(query('.container').children, function(box, i) {
setTimeout(function() {
box.classList.toggle('popIn');
}, 300 * i);
})
})
.container > .box {
width: 100px;
height: 100px;
background: cyan;
display: inline-block;
transform: scale(0);
transition: all 0.3s ease-in-out;
}
.container > .box.popIn {
transform: scale(1);
}
<button>Click</button>
<div class="container">
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
</div>
I have two boxes:
<div class='item' style='transform:translateY(100px)'>
</div>
<div class='item' style='transform:translateY(300px)'>
</div>
They both use the same class:
.item {
position:absolute;
background:red;
animation:float 3s ease-in-out infinite;
width:100px;
height:100px;
}
The animation looks like:
#keyframes float {
from, to { transform: translateY(-10px); }
50% { transform: translateY(10px); }
}
But this makes both boxes go between -10 and 10px. What I'd like is for it to be relative to the current value of the box.
So box1 at y:100px would animate from 90px to 110px
and box2 at y:300px would animate from 290px to 310px
Is it possible to do this in css? I'd rather not have a specific animation per box. Because I may have hundred of boxes.
jsfiddle:
https://jsfiddle.net/foreyez/1n1en8uk/
This shows that at the point of animation, both boxes are at the same place... but if it were relative animation they would just be floating up and down in their current place.
Note: please don't use top/left to get relative position I'm looking specifically for relative TRANSFORMS. (if you must know why I'm doing this in 3d for the z axis as x,y are already used).
You can set position of .item elements to relative; use :nth-of-type() pseudo selector to set the top property of each element to the initial position where element should be animated from between the 20px range in relation to its .item sibling.
.item {
position: relative;
background: red;
animation: float 3s infinite ease-in-out;
width: 100px;
height: 100px;
}
.item:nth-of-type(1) {
top: 100px;
}
.item:nth-of-type(2) {
top: 300px;
}
#keyframes float {
from, to {
transform: translateY(-10px);
}
50% {
transform: translateY(10px);
}
}
<div class="item">
</div>
<div class="item">
</div>
jsfiddle https://jsfiddle.net/1n1en8uk/3/
Here's a nasty javascript solution (yes, I'm looking for a CSS solution that's why I won't mark this as the answer) that injects a dynamic keyframe to the page:
As long as there are less than 100 boxes should work alright I guess.
https://jsfiddle.net/foreyez/6v7n4ro3/
function getTranslateY(obj)
{
var transformMatrix = obj.css("-webkit-transform") ||
obj.css("-moz-transform") ||
obj.css("-ms-transform") ||
obj.css("-o-transform") ||
obj.css("transform");
var matrix = transformMatrix.replace(/[^0-9\-.,]/g, '').split(',');
var x = matrix[12] || matrix[4];//translate x
var y = matrix[13] || matrix[5];//translate y
return parseInt(y);
}
function insertAnimation(idx, yy)
{
var style1 = document.documentElement.appendChild(document.createElement("style")),
rule1 = "#keyframes float" + idx + " {\
from, to { transform: translateY("+ (yy-10) +"px); }\
50% { transform: translateY(" + (yy+10) + "px); }\
}";
style1.sheet.insertRule(rule1, 0);
}
$(".item").each(function(idx) {
var currentTranslateY = getTranslateY($(this));
insertAnimation(idx, currentTranslateY);
$(this).css('animation','float' + idx + ' 3s ease-in-out infinite');
});
Note: please don't use top/left to get relative position I'm looking
specifically for relative TRANSFORMS.
You can adjust html to single .item element as child of a container element with transform set to translateY(100px), position set to absolute; utilize css :after pseudo element at .item element with transform set to translateY(200px)
.float {
transform: translateY(100px);
position: absolute;
}
.item {
animation: float 3s infinite ease-in-out;
}
.item, .item:after {
display: block;
position: relative;
background: red;
width: 100px;
height: 100px;
}
.item:after {
content: "";
transform: translateY(200px);
}
#keyframes float {
from, to {
transform: translateY(-10px);
}
50% {
transform: translateY(10px);
}
}
<div class="float">
<div class="item">
</div>
</div>
jsfiddle https://jsfiddle.net/1n1en8uk/5/
I'm using the below; but the margin isn't nearly as approximate as text-align:center; is there anyway I could toggle text-align: center; to occur with a animation transition until it gets to the point of text-align: center; similar to that which is achieved with the below.
if ($(".resource-section").hasClass("resource-section--expanded")) {
$(".resources__header h2").animate({"marginLeft": "40%"}, "slow");
}
You can use a centering technique that allows you to horizontally center any element.
It can be animated using only CSS
.test {
position: absolute;
transform: translateX(0%);
left: 0%;
animation: center 2s infinite;
}
#keyframes center {
0%, 10% {
transform: translateX(0%);
left: 0%;
}
90%, 100% {
transform: translateX(-50%);
left: 50%;
}
}
<h1 class="test">TEST</h1>
You could animate center align using margin-left like this
var h1 = $('h1').width();
var parent = $('.container').width();
$('h1').animate({'margin-left':(parent/2-h1/2)}, 1500);
.container {
border: 1px solid black;
height: 100px;
}
h1 {
display: inline-block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<h1>Text</h1>
</div>
From what i understood if you want to use the text-align:center property something like this could be of help to you, but the text-align property can't be animated
$(function(){
$('.resources_header h2').click(function(){
$(this).toggleClass('align-center');
});
});
.align-center{
text-align: center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<div class="resources_header">
<h2>HEADER</h2>
</div>
</div>
A problem with this is the animation so if what you want to achieve is to animate from left to center then why not:
$(function(){
$('.resources_header h2').click(function(){
var windowHalfWidth = $(window).width()/2;
$(this).css('position','absolute');
var elemHalfWidth = $(this).width()/2;
var left = windowHalfWidth - elemHalfWidth;
$(this).animate({
marginLeft: left
},"slow",function(){
$(this).css('position','static');
});
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<div class="resources_header">
<h2>HEADER</h2>
</div>
</div>
This is my first post here. I'm looking for some help.
I have multiple div contents which should fade in out and out dynamically. I have got this jsfiddle and made it work for 2 divs, but need it to work for multiple, for example 5 different DIVs.
Is this jsfiddle the best way to go or is there a better option. Here is the code. Here is the jquery.
var fadeinBox = $("#box2");
var fadeoutBox = $("#box1");
function fade() {
fadeinBox.stop(true, true).fadeIn(2000);
fadeoutBox.stop(true, true).fadeOut(2000, function() {
// swap in/out
var temp = fadeinBox;
fadeinBox = fadeoutBox;
fadeoutBox = temp;
// start over again
setTimeout(fade, 1000);
});
}
// start the process
fade();
Here is the html
<div id="wrapper">
<div id="box1" class="box"></div>
<div id="box2" class="box"></div>
</div>
and the CSS
.box {
position: absolute;
height: 100px;
width: 100px;
}
#wrapper {position: relative;}
#box1 {background-color: #F00;}
#box2 {background-color: #00F; display: none;}
Thank you in advance, and the link is below.
Dynamically changing DIV jsfiddle
You could use the same code but make it dynamic instead of two static elements:
var $boxes = $(".box").hide();
var current = 0;
function fade() {
$boxes.eq(current).stop(true, true).fadeOut(2000);
current = (current + 1) % $boxes.length;
$boxes.eq(current).stop(true, true).fadeIn(2000, function(){
setTimeout(fade, 1000);
});
}
fade();
http://jsfiddle.net/3XwZv/637/
What about doing it with CSS3 animations? Depending on your target browsers, this should work well:
Simplify your markup to a single div like this:
<div id="box"></div>
and then the CSS handles all the animation:
#keyframes colorCycle
{
0% {background-color:red;}
20% {background-color:orange;}
40% {background-color:yellow;}
60% {background-color:green;}
80% {background-color:blue;}
100% {background-color:purple;}
}
#-webkit-keyframes colorCycle /* Safari and Chrome */
{
0% {background-color:red;}
20% {background-color:orange;}
40% {background-color:yellow;}
60% {background-color:green;}
80% {background-color:blue;}
100% {background-color:purple;}
}
#box {
height: 100px;
width: 100px;
animation: colorCycle 10s 0s infinite;
-webkit-animation: colorCycle 10s 0s infinite;
}
Here's a Fiddle You can play with the key stops, timings, and colors to get the effect you need.
Depending on your specific application, you could toggle the fade for all boxes and use jQuery's promise() to determine when all boxes have finished fading.
HTML:
<div class="wrapper">
<div class="box box1"></div>
<div class="box box2"></div>
</div>
<div class="wrapper">
<div class="box box1"></div>
<div class="box box2"></div>
</div>
<div class="wrapper">
<div class="box box1"></div>
<div class="box box2"></div>
</div>
CSS:
.box {
position: absolute;
height: 100px;
width: 100px;
}
.wrapper {
position: relative;
width:100px;
height:100px;
margin-bottom:10px;
}
.box1 {
background-color: #F00;
}
.box2 {
background-color: #00F;
display: none;
}
JS:
function fade() {
jQuery('.box').stop(true, true).fadeToggle(2000);
jQuery('.box').promise().done(function(){fade();});
}
fade();
http://jsfiddle.net/3XwZv/638/