Hello and thank you in advance for reading my question.
GOAL: Set image so that once it's scrolled into view it transitions smoothly into a set position - but still reacts to :hover. Using #keyframes and a little JavaScript, I set the image to opacity: 0 and it's final opacity to opacity: .85. Then I added a hover effect in CSS to make it's opacity: 1
The issue is once it's finished with it's transition - it disappears - reverting to it's original opacity which is zero. I managed to make it freeze at .85 with animation-fill-mode: forwards, rather than animation-fill-mode: none, but then it won't respond to :hover
And here's a test snippet of the problem in action:
let observer_img = new IntersectionObserver(updates => {
updates.forEach(update => {
if (update.isIntersecting) {
update.target.classList.add('shift_frame_center_img');
} else {
update.target.classList.remove('shift_frame_center_img');
}
});
}, { threshold: 0 });
[...document.querySelectorAll('.features-img-wrapper img')].forEach(element => observer_img.observe(element));
body {
width: 100%;
height: 100vh;
background-color: lightgrey;
}
/* CHILD */
.features-img-wrapper img {
width: 10rem;
display: block;
margin-left: auto;
margin-right: auto;
margin-top: 8rem;
opacity: 0;
transition: all .5s;
}
/* APPEND-CHILD */
.shift_frame_center_img {
animation: center_img 1s 0.5s none;
}
/* CHILD ON HOVER */
.features-img-wrapper img:hover {
opacity: 1;
transform: scale(1.035);
}
/* KEYFRAMES */
#keyframes center_img {
0% {
transform: translateY(20rem);
}
100% {
transform: translateY(0);
opacity: .85;
}
}
<body>
<div class="features-img-wrapper">
<img src="https://synapse.it/wp-content/uploads/2020/12/test.png">
</div>
</body>
If I could get a hand with this that would be wonderful, I'm a bit of a beginner and have already spent a few hours on this, all feedback welcome. Thank you very much.
Solution 1
To understand why the hover effect was not working with the animation-fill-mode: forwards, read this answer.
You can fix that by adding !important property to the hover styles:
.features-img-wrapper img:hover {
opacity: 1 !important;
transform: scale(1.035) !important;
}
The problem, in this case, is that the transition will not work for hover.
Solution 2
You could remove the animation entirely and add the final state styles to the shift_frame_center_img class.
But you would still need to use the !important property because of the CSS Specificity.
let observer_img = new IntersectionObserver(updates => {
updates.forEach(update => {
if (update.isIntersecting) {
update.target.classList.add('shift_frame_center_img');
} else {
update.target.classList.remove('shift_frame_center_img');
}
});
}, { threshold: 0 });
[...document.querySelectorAll('.features-img-wrapper img')].forEach(element => observer_img.observe(element));
body {
width: 100%;
height: 100vh;
background-color: lightgrey;
}
/* CHILD */
.features-img-wrapper img {
width: 10rem;
transform: translateY(20rem);
display: block;
margin-left: auto;
margin-right: auto;
margin-top: 8rem;
opacity: 0;
transition: all .5s;
}
/* APPEND-CHILD */
.shift_frame_center_img {
transform: none !important;
opacity: .85 !important;
}
/* CHILD ON HOVER */
.features-img-wrapper img:hover {
opacity: 1 !important;
transform: scale(1.035) !important;
}
<body>
<div class="features-img-wrapper">
<img src="https://synapse.it/wp-content/uploads/2020/12/test.png">
</div>
</body>
This snippet removes the need for fill-mode forwards by setting the img to have opacity 1 as its initial state so it will revert to that at the end of the animation.
The animation itself is altered to take 1.5s rather than 1s with the first third simply setting the img opacity to 0 so it can't be seen. This gives the delay effect.
let observer_img = new IntersectionObserver(updates => {
updates.forEach(update => {
if (update.isIntersecting) {
update.target.classList.add('shift_frame_center_img');
} else {
update.target.classList.remove('shift_frame_center_img');
}
});
}, { threshold: 0 });
[...document.querySelectorAll('.features-img-wrapper img')].forEach(element => observer_img.observe(element));
body {
width: 100%;
height: 100vh;
background-color: lightgrey;
}
/* CHILD */
.features-img-wrapper img {
width: 10rem;
display: block;
margin-left: auto;
margin-right: auto;
margin-top: 8rem;
opacity: 0;
transition: all .5s;
opacity: 1;
}
/* APPEND-CHILD */
.features-img-wrapper img {
animation: center_img 1.5s 0s none;
}
/* CHILD ON HOVER */
.shift_frame_center_img:hover {
opacity: 1;
transform: translateY(0) scale(1.035);
}
/* KEYFRAMES */
#keyframes center_img {
0% {
transform: translateY(20rem) scale(1);
opacity: 0;
}
33.33% {
transform: translateY(20rem) scale(1);
opacity: 0;
}
100% {
transform: translateY(0) scale(1);
opacity: .85;
}
}
<body>
<div class="features-img-wrapper">
<img src="https://synapse.it/wp-content/uploads/2020/12/test.png">
</div>
</body>
Note: as each transform setting will reset anything that isn't included both tranlateY and scale are included in each setting.
Outside the SO snippet system it was possible to leave the animation settings untouched by chaining another animation to the front which ran for 0.5s and just set the img to opacity: 0. This did not work in the snippet system (it got into a loop of flashing on and off) hence the introduction of one but extended animation.
Related
I managed to achieve this animation through CSS animations and React. but go stuck in flickering problem. I don't know why this is happening maybe 'cause I used setInterval or there is some problem with my css animations keyframes. help me to solve this problem. the flicker only occurs after some time. and after refreshing the page the animation works perfectly fine without flicker problem.
this is how the animation looks after refreshing the page and this is what I want too. (ignore the screen recorder watermark).
animation I want
but after sometime the animation starts flickering like this.
Flickering problem
here are the code snippets I wrote
jsx snippet
<div className="relative w-[280px] md:w-[350px] lg:w-[500px]">
<span>{"[ "}</span>
<p className="text_animate ml-2">
{dev ? "for" : "by"} Developers
</p>
<span className="absolute right-0 ">{" ]"}</span>
</div>
css snippet
.text_animate {
color: orange;
margin: 0 auto;
display: inline-block;
position: relative;
letter-spacing: .15em;
text-align: start;
animation: text-up 6s linear infinite;
cursor: none;
}
#keyframes text-up {
0% {
top:45px;
opacity: 0;
}
20% {
top:0;
opacity: 1;
}
35% {
top: 0;
opacity: 1;
}
50% {
top: -45px;
opacity: 0;
}
52% {
top: 45px;
opacity: 0;
}
70% {
top: 0;
opacity: 1;
}
85% {
top: 0;
opacity: 1;
}
100% {
top: -45px;
opacity: 0;
}
}
useState changing text
const [dev, setDev] = useState(true);
setInterval(() => {
setDev(!dev);
}, 3000);
If there is any better way to achieve this I would really love to learn so let me know that too.
Maybe you should put setInterval to useEffect, and remember to clear timer. Like this:
useEffect(() => {
const timer = setInterval(() => {
setDev(!dev);
}, 3000);
return () => {
clearInterval(timer);
}
}, []);
And there is a solution only using css to implement this, I will write a demo later.
EDIT:
Explain the above code:
useEffect with [] as second param will make sure run setInterval once when mount the component.
The clearInterval in return function will make sure engine GC the variables in setInterval callback functions when unmount the component, or the setInterval will still work even though you needn't it.
CSS only solution:
ul {
margin: 0;
padding: 0;
list-style-type: none;
}
li {
margin: 0;
padding: 0;
}
.scroll-container {
overflow: hidden;
height: calc(var(--line-h) * 1px);
line-height: calc(var(--line-h) * 1px);
font-size: 18px;
}
.scroll-container ul {
animation-name: move;
animation-duration: calc(var(--speed) * var(--lines));
animation-timing-function: steps(var(--lines));
animation-iteration-count: infinite;
}
.scroll-container ul li {
animation-name: li-move;
animation-duration: var(--speed);
animation-iteration-count: infinite;
}
#keyframes move {
0% {
transform: translate(0, 0);
}
100% {
transform: translate(0, calc(var(--lines) * var(--line-h) * -1px));
}
}
#keyframes li-move {
0% {
transform: translate(0, 0);
}
50%,
100% {
transform: translate(0, calc(var(--line-h) * -1px));
}
}
<div
class="scroll-container"
style="--lines: 2; --line-h: 26; --speed: 3s"
>
<ul>
<li>For Developers</li>
<li>By Developers</li>
<!-- repeat first in tail for infinity -->
<li>For Developers</li>
</ul>
</div>
I leaned this from Chokcoco on CodePen but forget which post.
I want my drawer to animate on the way in, and on the way out and when the animation ends, to turn to display: none but when the drawer is closed, it disappears and doesn't animate out.
const Drawer = ({ closeDrawer, isDrawerOpen }) => {
const [isAnimating, setIsAnimating] = useState()
let drawerClassName
if (isDrawerOpen) {
drawerClassName = "drawer-in"
} else if (!isDrawerOpen && isAnimating) {
drawerClassName = "drawer-animating"
} else if (!isDrawerOpen && !isAnimating) {
drawerClassName = "drawer-out"
}
return (
<>
<div
className={`drawer ${drawerClassName}`}
onAnimationStart={() => setIsAnimating(true)}
onAnimationEnd={() => setIsAnimating(false)}
></div>
<div onClick={closeDrawer}></div>
</>
)
}
CSS:
.drawer {
height: 100%;
width: 60%;
background-color: #fff;
position: absolute;
right: 0;
top: 0;
opacity: 1;
z-index: 3;
transform: translateX(100%);
}
.drawer-in {
animation: 0.7s drawerIn;
transform: translateX(0);
display: block;
}
.drawer-animating {
animation: 0.7s drawerOut;
display: block;
}
.drawer-out {
animation: 0.7s drawerOut;
display: none;
}
#keyframes drawerIn {
0% {
transform: translateX(100%);
}
1% {
transform: translateX(100%);
}
100% {
transform: translateX(0);
}
}
#keyframes drawerOut {
0% {
transform: translateX(0);
}
99% {
transform: translateX(100%);
}
100% {
transform: translateX(100%);
}
}
it's because when you set the drawer-out class to the element display: none; is activated immediately and you don't see the animation.
One solution is to run a setTimeout function within your JavaScript to wait for the animation to finish, then change the elements display property to none. This will allow your 'closing animation' to complete before removing the element. See my snippet below for working example.
Basically what's happening in my example snippet is I'm triggering the closing animation by adding a class associated with the animation. Then I'm setting a time out function that waits for the animation to complete (set time out in milliseconds to match your animation time within your CSS). Once the timeout is complete, the animation class is removed and the element's data attribute is set to closed which will trigger the display none. Hope this helps.
const menu = document.querySelector('.menu');
const menuToggle = document.querySelector('.menu_toggle');
menuToggle.checked=false
menuToggle.addEventListener('change',(e)=>{
menuToggle.disabled=true
let menuState = menu.dataset.menuState
if(menuState==='closed'){
menu.dataset.menuState='opened'
setTimeout(() => {
menuToggle.disabled=false
}, 500);
}else{
menu.classList.add('animate_close')
setTimeout(() => {
menu.classList.remove('animate_close')
menu.dataset.menuState='closed'
menuToggle.disabled=false
}, 500);
}
})
body {
background-color: rgb(235, 235, 235);
}
.menu {
background-color: black;
color: white;
width: fit-content;
}
.menu {
transition: all .5s ease-in-out;
}
.menu[data-menu-state="closed"] {
background-color: red;
display: none;
}
.menu[data-menu-state="opened"] {
animation: openMenu .5s ease-in-out;
transform: translateX(100%);
background-color: green;
}
.menu.animate_close{
background-color: rgb(0, 30, 128);
animation: closeMenu .5s ease-in-out;
opacity: 0;
}
#keyframes openMenu {
0% {
transform: translateX(0);
}
100% {
transform: translateX(100%);
}
}
#keyframes closeMenu {
0% {
transform: translateX(100%);
}
100% {
transform: translateX(0);
}
}
<body>
<label for="menu_toggle">Menu Toggle</label>
<input id="menu_toggle" type="checkbox" class="menu_toggle">
<div class="menu_container">
<div class="menu" data-menu-state="closed">
<ul>
<li>Item1</li>
<li>Item2</li>
<li>Item3</li>
</ul>
</div>
</div>
<script src="main.js"></script>
</body>
If I set the css animation to "element", it's fine. If the Css animation is not available, how does it function fire like a zero seconds animation in the ES5?
function run() { ... }
element.addEventListener('animationend', run);
Reply for
#Anurag Srivastava,
Am I wrong idea or do I have the following code wrong? Either way, the return value is "".
var el1 = document.getElementById("notAnimation");
console.log(el1.style.animation);
var el2 = document.getElementById("onAnimation");
console.log(el2.style.animation);
div {
padding: 10px;
margin: 20px;
}
#notAnimation {}
#onAnimation {
animation: scale 10s ease-in-out;
}
#keyframes scale {
0% {
transform: scale(1);
opacity: 1;
color: black;
}
50% {
transform: scale(0.95);
opacity: .4;
color: red;
}
100% {
transform: scale(1);
opacity: 1;
color: black;
}
}
<div id="notAnimation">
Not Animation
</div>
<div id="onAnimation">
Animation
</div>
You can check if element.style.WebkitAnimation and element.style.animation contain any value and execute run() if the value is ""
Edit Turns out that .style will return "" for any value. What you need is window.getComputedStyle() along with the property animationName. If it is none, there is no animation, else there is. Check the code below:
var el1 = document.getElementById("notAnimation");
console.log(window.getComputedStyle(el1)["animationName"])
var el2 = document.getElementById("onAnimation");
console.log(window.getComputedStyle(el2)["animationName"])
div {
padding: 10px;
margin: 20px;
}
#notAnimation {}
#onAnimation {
animation: scale 10s ease-in-out;
}
#keyframes scale {
0% {
transform: scale(1);
opacity: 1;
color: black;
}
50% {
transform: scale(0.95);
opacity: .4;
color: red;
}
100% {
transform: scale(1);
opacity: 1;
color: black;
}
}
<div id="notAnimation">
Not Animation
</div>
<div id="onAnimation">
Animation
</div>
So, I have created a CSS3 animation that is supposed to fade out an element by setting its opacity from 1 to 0 and at the last frames change the position to absolute and display to none. But on Safari it will only maintain the opacity, position and display are not set to the final values.
#-webkit-keyframes impressum-fade-out {
0% {
opacity: 1;
display: block;
position: relative;
}
99% {
opacity: 0;
position: relative;
}
100% {
opacity: 0;
display: none;
position: absolute;
}
}
It seems to work on Chrome but not on Safari (I tried version 8). Apparently, position and display do not work properly with animation-fill-mode: forwards...
JSFiddle: http://jsfiddle.net/uhtL12gv/
EDIT For Bounty: I am aware of workarounds with Javascript and transitionend events. But I am wondering why Browsers lack support for this? Does the specification state that fillmode forwards doesnt apply to some attributes like position or is this a bug in the browsers? Because I couldnt find anything in the bug trackers.. If anybody has some insight, I would really appreciate it
As Suggested in the comments, you can adjust the height.
EDIT: Animation Reference Links Added.
Display property is not animatable.
Position property is not
animatable.
List of all CSS properties and if and how they are
animatable.
$('.block').click(function() { $(this).toggleClass('active') });
#-webkit-keyframes impressum-fade-out {
0% {
opacity: 1;
}
99% {
opacity: 0;
}
100% {
opacity: 0;
height:0;
}
}
.block {
width: 100px;
height: 100px;
background: blue;
}
.block2 {
width: 100px;
height: 100px;
background: red;
}
.block.active {
-webkit-animation-name: impressum-fade-out;
animation-name: impressum-fade-out;
-webkit-animation-duration: 500ms;
animation-duration: 500ms;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div class="block"></div>
<div class="block2"></div>
I would suggest you the cross-browser solution based on CSS3 Transitions and transitionend event:
JSFiddle
$('.block').one('click', function() {
var $this = $(this);
$this.one('webkitTransitionEnd transitionend', function() {
$this.addClass('block_hidden');
$this.removeClass('block_transition');
});
$this.addClass('block_transition');
});
.block {
width: 100px;
height: 100px;
background: blue;
-webkit-transition: opacity 0.5s;
transition: opacity 0.5s;
}
.block_2 {
background: red;
}
.block_transition {
opacity: 0;
}
.block_hidden {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="block"></div>
<div class="block block_2"></div>
I'm trying to create the following effect for any element using only jQuery/plugins:
In particular it should use a transform for the scale rather than width and height animation and be usable on any DOM element.
Is there a plugin available for jQuery which will achieve this effect? It should be quite simple: duplicate the dom object with clone(), reposition the clone over the original absolutely then animate a scale transform and opacity on the new element. However, I suspect it's not as simple as this.
Any ideas?
You don't need jQuery to accomplish that animation. You can use CSS3 animations and transform properties. Check out the following example I created:
http://jsbin.com/purik/1/
HTML:
<div class="logos">
<div class="logo"></div>
<div class="logo animated"></div>
</div>
CSS:
.logos {
position: relative;
}
.logo {
width: 100px;
height: 70px;
background: #CC0000 url(http://www.w3schools.com/images/w3logo.png) 50% 50% no-repeat;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.logo.animated {
position: absolute;
left: 0;
top: 0;
animation: scale-fadeout 2s infinite;
-webkit-animation: scale-fadeout 2s infinite;
}
#keyframes scale-fadeout {
0% {
transform: scale(1);
opacity: 1;
}
5% {
opacity: 1;
transform: scale(1.05);
}
100% {
opacity: 0;
transform: scale(1.35);
}
}
#-webkit-keyframes scale-fadeout {
0% {
-webkit-transform: scale(1);
opacity: 1;
}
5% {
opacity: 1;
-webkit-transform: scale(1.05);
}
100% {
opacity: 0;
-webkit-transform: scale(1.35);
}
}
This works if the parent element is position: relative, and the element itself is position: absolute.
Clones the element and then animates it to change the size, change the values of left and top, and the set opacity: 0.
Fiddle: http://jsfiddle.net/Ej38P/1/