JavaScript:
const show = entries => entries[0].isIntersecting ? entries[0].classList.remove('is-hidden') : null;
const observer = new IntersectionObserver(show, {threshold:0});
Array.from(document.querySelectorAll('.js-hidden')).forEach(element => observer.observe(element));
I want to play fade in animation when '.js-hidden' class are removed like below... but this code are not working:
HTML:
<div class="my-component js-hidden is-hidden">
<p class="text text-1">Hello</p>
<p class="text text-2">World</p>
</div>
Stylus:
fadeIn(duration=1s, delay=0s) {
opacity: 1;
transition: opacity duration ease delay;
&.is-hidden { // <- yeah, this is wrong... but, any ideas? I want to apply transition both element and pseudo element.
opacity: 0;
}
}
.my-component {
.text,
&::before,
&::after {
fadeIn();
}
.text-2 {
transition-delay: .3s;
}
&::before {
content: 'foo';
transition-delay: .5s;
}
&::after {
content: 'bar';
transition-delay: .7s;
}
}
And, if the fade in elements are more nested?
<div class="my-component js-hidden is-hidden">
<div class="wrapper-1">
<div class="wrapper-2">
<p class="text text-1">Hello</p>
<p class="text text-2">World</p>
</div>
</div>
</div>
I want to apply transition both element and pseudo element. but I don’t know what should I do...
Thanks.
Finally...
Thank you Andy.
Finally I arrived the code below. XD
JavaScript:
const show = entries => entries[0].isIntersecting ? entries[0].target.classList.remove('is-hidden') : null;
const observer = new IntersectionObserver(show, {threshold:0});
Array.from(document.querySelectorAll('.js-hidden')).forEach(element => observer.observe(element));
HTML:
<div class="my-component js-hidden is-hidden">
<p class="text text-1">Hello</p>
<p class="text text-2">World</p>
<p class="text text-3">Hello</p>
<p class="text text-4">World</p>
<p class="text text-5">Hello</p>
<p class="text text-6">World</p>
<p class="text text-7">Hello</p>
<p class="text text-8">World</p>
<p class="text text-9">Hello</p>
</div>
Stylus:
fadeIn(target, duration=1s, delay=0s, property=all, easing=ease) {
{target} {
opacity: 1;
transition: duration property easing delay;
}
&.is-hidden {
{target} {
opacity: 0;
}
}
}
.my-component {
duration = .3s;
delay = 1s;
fadeIn('.text', duration:duration, delay:delay);
fadeIn('&::before', duration:duration, delay:delay);
fadeIn('&::after', duration:duration, delay:delay);
interval = duration;
amount = 9;
for i in 2..amount {
.text-{i} {
transition-delay: (interval * (i - 2) + duration + delay)s;
}
}
&::before {
content: 'FOOOOOOOOOOOOOOO';
transition-delay: (interval * ((amount + 1) - 2) + duration + delay)s;
}
&::after {
content: 'BARRRRRRRRRRRRRR';
transition-delay: (interval * ((amount + 2) - 2) + duration + delay)s;
}
}
First, it seems you're looking for a transition, not an animation. I'm not a Stylus expert, but know IntersectionObserver and CSS pretty well. I have the basic demo working now.
Some notes on the adjusted fadeIn function.
is-hidden is a class that exists in the DOM from the beginning, so cue the transition when it's not there
use a delegate pattern from transitions—that is, have the change in the parent affect the children (don't listen for a class for each child/pseudo element)
fadeIn(duration=1s, delay=0s) {
.text,
&::before,
&::after {
opacity: 0;
transition: 0.5s opacity ease;
}
&:not(.is-hidden) {
.text,
&::before,
&::after {
opacity: 1;
}
}
}
Also, I couldn't get your JavaScript to work due to some errors and rewrote it to suit the demo. Here's the rewritten JavaScript:
const components = document.querySelectorAll(".my-component");
const observer = new IntersectionObserver(components => {
components.forEach(component => {
if (component.intersectionRatio > 0) {
component.target.classList.remove("is-hidden")
} else {
component.target.classList.add("is-hidden")
}
})
});
Array.from(document.querySelectorAll(".js-hidden")).forEach(element =>
observer.observe(element)
);
CodePen Demo
Related
I have animation of icon that is rotating.
It may be finished any unexpected time.
So my question is. Is there any way using pure CSS to finish it to the end keyframe before stop rotating?
I have this code right now:
I did tried transition without any good result:
.loop {
transition: 400ms;
transform: rotate(180deg);
&:hover {
animation: rotation 400ms infinite linear;
}
}
#keyframes rotation {
from {
transform: rotate(180deg);
}
to {
transform: rotate(0deg);
}
}
I wrote this directive, that works as I expect.
import { Directive, ElementRef, Input } from '#angular/core';
#Directive({
selector: '[appRotate]'
})
export class RotateDirective {
speedQueue: Array<number>= [];
_speed = 0;
#Input('appRotate') set speed(value: number = 0) {
const steps = 5;
const step = (value - this._speed) / steps;
this.speedQueue = new Array(steps).fill(0).map((_, i) => (i+1) * step + this._speed);
}
constructor(private el: ElementRef) {
let rot = 0;
const loop = () => {
if (this.speedQueue.length) {
this._speed = this.speedQueue.shift();
}
rot += this._speed * 360;
this.el.nativeElement.style.transform = `rotate(${rot % 360}deg) scaleX(-1)`;
requestAnimationFrame(loop);
};
requestAnimationFrame(loop);
}
}
<span class="material-icons loop" [appRotate]="speed">
loop
</span>
<div>
<input type="radio" name="speed" (click)="speed=0">0
<input type="radio" name="speed" (click)="speed=0.02">2%
<input type="radio" name="speed" (click)="speed=0.08">8%
</div>
It works like that:
It is not CSS solution.
I have a child component where I would like to use slide-out animation as new props are getting passed to it and I try to use react-transition-group/switch-transition but is not really clear how to use it
The child component render method looks as it follows
return (
<SwitchTransition mode="out-in">
<CSSTransition
classNames="slide"
>
<div className={classnames("fields-group", containerClass)}>
{/* <pre>{JSON.stringify(this.props.fields, null, 2)}</pre>*/}
{fields}
</div>
</CSSTransition>
</SwitchTransition>
);
There are more things you need to do:
CSSTransition should has a prop key. When it changed, the transition will take affect.
You need to add the transition styles by yourself because, React Transition Group is not an animation library like React-Motion, it does not animate styles by itself. reference
So the child component will look something like that:
function Child({ propToAnimate }) {
return (
<>
<h4>Child Component</h4>
<div className="main">
<SwitchTransition mode="out-in">
<CSSTransition
key={propToAnimate}
addEndListener={(node, done) => {
node.addEventListener("transitionend", done, false);
}}
classNames="fade"
>
<div className="button-container">
<div className="animate">
<pre>state: {propToAnimate}</pre>
</div>
</div>
</CSSTransition>
</SwitchTransition>
</div>
</>
);
}
And the styles (for slide animation for example):
.fade-enter .animate {
opacity: 0;
transform: translateX(-100%);
}
.fade-enter-active .animate {
opacity: 1;
transform: translateX(0%);
}
.fade-exit .animate {
opacity: 1;
transform: translateX(0%);
}
.fade-exit-active .animate {
opacity: 0;
transform: translateX(100%);
}
.fade-enter-active .animate,
.fade-exit-active .animate {
transition: opacity 500ms, transform 500ms;
}
https://codesandbox.io/s/switchtransition-child-component-dk4jo
I have many div stacked up in a column. When top div is destroyed lower divs will naturally come up and vice versa. I want that transition to be smooth.
How do I achieve this? Do I apply CSS animation class to each inserted div? I am confused. Pls advice. Thanks.
$add = document.getElementById('add');
$remove = document.getElementById('remove');
$parent = document.getElementById('parent');
let i = 0
$add.onclick = function() {
let el = document.createElement("DIV");
let text = document.createTextNode("Hello " + i);
el.appendChild(text);
$parent.insertBefore(el, $parent.firstElementChild);
i++;
}
remove.onclick = function() {
$parent.removeChild($parent.firstElementChild);
}
<button id="add" onclick="myFunction()">Add child on top</button>
<button id="remove" onclick="removeChlid()">remove child on top</button>
<div id="parent">
</div>
Animation can be achieved using css if we are adding element.
In case of removing we need to fisrt add class before removing to start animation and then remove element from DOM when animation is done. In example there is the same 500m delay, of course it can be done also checking animationEnd event in js.
$add = document.getElementById('add');
$remove = document.getElementById('remove');
$parent = document.getElementById('parent');
let i = 0
$add.onclick = function(){
let el = document.createElement("DIV");
el.className='test';
let text = document.createTextNode("Hello "+ i );
el.appendChild(text);
$parent.insertBefore(el, $parent.firstElementChild);
i++;
}
remove.onclick = function(){
$parent.firstElementChild.className += ' remove-animation';
setTimeout(() => {
$parent.removeChild($parent.firstElementChild);
}, 500)
}
.test {
max-height: auto;
animation: test-animation 0.5s;
transition: max-height 0.5s;
margin-bottom: 5px;
overflow: hidden;
}
.remove-animation {
animation: remove-animation 0.5s;
}
#keyframes test-animation {
0% { max-height: 0; }
100% { max-height: 25px }
}
#keyframes remove-animation {
0% { max-height: 25px; }
100% { max-height: 0 }
}
<button id="add" onclick="myFunction()">Add child on top</button>
<button id="remove" onclick="removeChlid()">remove child on top</button>
<div id="parent">
</div>
I am trying to display a div with the click of a button with slide effect. When something is clicked, it will toggle as shown or invisible with slide effect. I have achieved this so far by doing this.
class App extends Component {
constructor() {
super();
this.state = {
myclass: '',
}
this.divclicked = this.divclicked.bind(this);
}
divclicked() {
if (this.state.myclass === '') {
this.setState({
myclass: 'coolclass'
})
}
else {
this.setState({
myclass: '',
})
}
}
render() {
return (
<div className="App">
<div id="box" onClick={this.divclicked}>
</div>
<div id="seconddiv" className={this.state.myclass}>
<p>help help</p>
<p>help help</p>
<p>help help</p>
<p>help help</p>
<p>help help</p>
</div>
</div>
);
}
}
export default App;
And my CSS
#box {
height: 50px;
width: 50px;
background-color: red
}
#seconddiv.coolclass{
height:300px;
background: purple;
}
#seconddiv {
height: 0px;
transition: 0.5s;
overflow: hidden;
}
So here, when the red box with the id of "box" is clicked, I give the "seconddiv" a className and CSS takes care of hiding the div. The problem is that when I am giving the className coolclass, I do not want to use px but want to use percentage. So currently, it is going from 0px to 300px. But I want it to go from 0px to 100%; How do I achieve this. When I try to put the height of 100% in seconddiv, the slide animation doesn't work.
You need to set your initial height to 0%. You also need to give .App a height of 100% so that it stretches the full height of the window. In this example, I gave it a static height of 1200px, but that should be determined by the body.
class App extends React.Component {
constructor() {
super();
this.state = {
myclass: '',
}
this.divclicked = this.divclicked.bind(this);
}
divclicked() {
if (this.state.myclass === '') {
this.setState({
myclass: 'coolclass'
})
}
else {
this.setState({
myclass: '',
})
}
}
render() {
return (
<div className="App">
<div id="box" onClick={this.divclicked}>
</div>
<div id="seconddiv" className={this.state.myclass}>
<p>help help</p>
<p>help help</p>
<p>help help</p>
<p>help help</p>
<p>help help</p>
</div>
</div>
);
}
}
ReactDOM.render(<App/>,document.getElementById('root'));
#box {
height: 50px;
width: 50px;
background-color: red
}
#seconddiv.coolclass{
max-height:100%;
background: purple;
}
#seconddiv {
max-height: 0%;
transition: 0.5s;
overflow: hidden;
}
.App {
height: 100%;
}
#root {
height: 1200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
You can just combine conditional rendering ( x && y ) with some scale animation
Example
const scaleAnimationIn = keyframes`
0% {
transform: scale(0, 1);
animation-timing-function: ease-out;
}
100% {
transform: scale(1, 1);
}
`
const QuestionHeaderPausedText = styled.div`
animation: ${scaleAnimationIn} 1s;
animation-delay: 7s;
animation-fill-mode: both;
`
In the return/render method just:
{isSomeConditionTrue && <QuestionHeaderPausedText>
My text
</QuestionHeaderPausedText>}
This example use Styled Components but the method (scaling) will work with any css solution.
First, a fiddle: http://jsfiddle.net/AATLz/
The essence here is that there's a set of animations queued using -webkit-transition-delay. First element 0.4s, second 0.8s, third 1.4s, etc. They're queued last to first by default, and first to last when the parent has the 'expanded' class (toggled with that button).
This means that the animation when '.expanded' is added brings the boxes out one by one, and in reverse when the class is removed.
That's dandy. The problems start to arise when the class is toggled mid-animation. If you toggle, say, after the second box has animated, there's a delay before they start animating back, because a couple delay timers are being waited out.
Delays are obviously a bit clunky here.
The two alternatives I have in mind are 1) CSS keyframe animations, which I'm not entirely sure of how to activate on multiple elements in succession, and 2), JS controlled timing - using something like jQuery Transit. I'm not sure which would be more capable/graceful or if I'm missing another option.
Any input would be awesome!
jsfiddle: http://jsfiddle.net/Bushwazi/fZwTT/
Changed it up a bit. Control the timing with js. Animations with css.
CSS:
* {
margin:0;
padding:0;
}
#container {
background: orange;
height: 100px;
position: relative;
width: 100px;
}
.box {
height: 100px;
left: 0;
position: absolute;
top: 0;
width: 100px;
-webkit-transition:all 0.5s ease-in-out 0s;
-moz-transition:all 0.5s ease-in-out 0s;
-o-transition:all 0.5s ease-in-out 0s;
transition:all 0.5s ease-in-out 0s;
-webkit-transform: translate3d(0,0,0);
}
.box-1 {
background: red;
}
.box-2 {
background: green;
}
.box-3 {
background: yellow;
}
.box-4 {
background: blue;
}
.box-1 .box-1 {
left:100px;
}
.box-2 .box-2 {
left:200px;
}
.box-3 .box-3 {
left:300px;
}
.box-4 .box-4 {
left:400px;
}
HTML:
<div id="container" class="box-0" data-status="default" data-box="0">
<div class="box box-1"></div>
<div class="box box-2"></div>
<div class="box box-3"></div>
<div class="box box-4"></div>
</div>
<button id="ToggleAnim">Toggle</button>
JS:
(function(){
var testies = {
to: 0,
init: function(){
$button = $('#ToggleAnim');
$anim_elm = $('#container');
$(function(){
testies.el();
});
},
el: function(){ // el ==> event listeners
$button.on('click', testies.toggleBoxes);
},
toggleBoxes: function(evt){
var status = $anim_elm.attr('data-status'),
pos = $anim_elm.attr('data-box');
window.clearTimeout(testies.to);
// if default ==> build
// if killing ==> build
// if building ==> kill
// if done ==> kill
if(status == 'build' || status == 'done'){
testies.kill();
} else {
testies.build();
}
evt.preventDefault();
},
build: function(){
bpos = $anim_elm.attr('data-box');
if(bpos < 4){
bpos++;
$anim_elm.attr('data-status', "build").attr('data-box', bpos).addClass('box-' + bpos);
testies.to = window.setTimeout(testies.build, 500);
}
if(bpos == 4)$anim_elm.attr('data-status', "done");
console.log('BUILD: ' + bpos);
},
kill: function(){
kpos = $anim_elm.attr('data-box');
if(kpos > 0){
db = kpos - 1;
$anim_elm.attr('data-status', "kill").attr('data-box', db).removeClass('box-' + kpos);
testies.to = window.setTimeout(testies.kill, 500);
}
console.log('KILL: ' + kpos);
if(kpos == 0)$anim_elm.attr('data-status', "default")
}
}
testies.init();
})();