I want to use animation in my app, but it requires #property function from SCSS:
#property --border-angle {
syntax: "<angle>";
inherits: true;
initial-value: 0turn;
}
Is there a way to do it in styled-components?
The whole code for animation is on: https://codepen.io/shshaw/pen/RwJwJJx
Or how to re-write this function so it does not have to use property function?
The posted code does seem to work with styled-components as I tested, although it seems that the browser support for #property is still limited, such as it works for Chrome but not currently for Firefox, therefore the gradient animation will not play on it.
I tried to create an alternative version of the posted code without the use of #property, which runs on Firefox as well. In case if it could be useful, here is a demo on: stackblitz (code included at the end of the answer).
The original posted code was tested with below example on: stackblitz (gradient animation by #property not currently supported by Firefox).
// Styled components
const Container = styled.div`
height: 100%;
background: #223;
display: grid;
place-items: center;
`;
const Box = styled.div`
--border-size: 3px;
--border-angle: 0turn;
width: 60vmin;
height: 50vmin;
background-image: conic-gradient(
from var(--border-angle),
#213,
#112 50%,
#213
),
conic-gradient(from var(--border-angle), transparent 20%, #08f, #f03);
background-size: calc(100% - (var(--border-size) * 2))
calc(100% - (var(--border-size) * 2)),
cover;
background-position: center center;
background-repeat: no-repeat;
animation: bg-spin 3s linear infinite;
#keyframes bg-spin {
to {
--border-angle: 1turn;
}
}
&:hover {
animation-play-state: paused;
}
#property --border-angle {
syntax: "<angle>";
inherits: true;
initial-value: 0turn;
}
`;
export default function App() {
return (
<Container>
<Box></Box>
</Container>
);
}
Below is the alternative version without #property for comparison, it used pseudo-element and added a child div to recreate the animation in styled-components.
Live demo on: stackblitz (should also work for Firefox).
// Styled components
const Container = styled.div`
min-height: 100vh;
background: #223;
display: grid;
place-items: center;
`;
const Box = styled.div`
width: 60vmin;
height: 50vmin;
position: relative;
overflow: hidden;
&::before {
content: "";
position: absolute;
inset: 0;
background-image: conic-gradient(from 0turn, transparent 20%, #08f, #f03);
animation: fallback-spin 3s linear infinite;
}
#keyframes fallback-spin {
to {
transform: scale(1000%) rotate(1turn);
}
}
&:hover::before {
animation-play-state: paused;
}
&:hover > div::before {
animation-play-state: paused;
}
`;
const Fallback = styled.div`
position: absolute;
inset: 3px;
overflow: hidden;
background-color: pink;
&::before {
content: "";
position: absolute;
inset: 0;
background-image: conic-gradient(from 0turn, #213, #112 50%, #213);
animation: fallback-spin 3s linear infinite;
}
#keyframes fallback-spin {
to {
transform: scale(1000%) rotate(1turn);
}
}
`;
export default function App() {
return (
<Container>
<Box>
<Fallback></Fallback>
</Box>
</Container>
);
}
#property is newer but standard CSS, by the way. More background about #property on MDN.
Related
I created a simple HTML game, which disappears under the screen when I click on a moving box.
However, the animation that disappears starts at the original location, not where it was clicked.
I think 0% of the remove #keyframes should have the location of the click, but I couldn't find a way
How shall I do it?
(function () {
const charactersGroup = document.querySelectorAll('.character');
const stage = document.querySelector('.stage')
const clickHandler = (e) => {
const target = e.target;
if (target.classList.contains('character')) {
target.classList.remove(`f${target.dataset.id}`);
target.classList.add('f0');
target.classList.add('remove');
setTimeout(() => { stage.removeChild(target) }, 2000);
}
}
stage.addEventListener('click', clickHandler);
}());
.stage {
overflow: hidden;
position: relative;
background: #eeeeaa;
width: 40vw;
height: 20vw;
}
#keyframes moving {
0% {
transform: translateX(0);
}
100% {
transform: translateX(30vw);
}
}
#keyframes remove {
0% {
transform: translate(0);
}
100% {
transform: translateY(60vw);
}
}
.character {
position: absolute;
width: 50px;
height: 50px;
background-repeat: no-repeat;
background-position: 50% 50%;
background-size: contain;
animation: moving infinite alternate;
}
.remove {
animation: remove 0.2s cubic-bezier(.68,-0.55,.27,1.55) forwards;
}
.f0 {
background-color: black;
animation-duration: 2s;
}
.f1 {
left: 5%;
bottom: 5%;
animation-duration: 2s;
background-color: red;
}
<div class="stage">
<div class="character f1" data-id="1"></div>
</div>
Change the first animation to consider left instead of translate then append both of them to the element initially and you simply toggle the animation-play-state when adding the remove class
(function() {
const charactersGroup = document.querySelectorAll('.character');
const stage = document.querySelector('.stage')
const clickHandler = (e) => {
const target = e.target;
if (target.classList.contains('character')) {
target.classList.add('remove');
setTimeout(() => {
stage.removeChild(target)
}, 2000);
}
}
stage.addEventListener('click', clickHandler);
}());
.stage {
overflow: hidden;
position: relative;
background: #eeeeaa;
width: 40vw;
height: 20vw;
}
#keyframes moving {
100% {
left:calc(95% - 50px);
}
}
#keyframes remove {
50% {
transform: translateY(-30vh);
}
100% {
transform: translateY(60vw);
}
}
.character {
position: absolute;
width: 50px;
height: 50px;
background:red;
left: 5%;
bottom: 5%;
animation:
moving 2s infinite alternate,
remove 1s cubic-bezier(.68, -0.55, .27, 1.55) forwards paused;
}
.remove {
animation-play-state:paused,running;
background: black;
}
<div class="stage">
<div class="character f1" data-id="1"></div>
</div>
If your use case is to deal with a lot of such boxes and complexity, it's better to go with handling everything with pure JS but I tried to make this work with minimal changes in JS and CSS.
I have added comments to the new JS lines.
Also taken the liberty to have a separate class with name moving for animation moving so that we can remove it on click.
(function () {
const charactersGroup = document.querySelectorAll('.character');
const stage = document.querySelector('.stage')
const clickHandler = (e) => {
const target = e.target;
if (target.classList.contains('character')) {
target.classList.remove(`f${target.dataset.id}`);
target.classList.add('f0');
// remove the moving animation
target.classList.remove('moving');
// Get offsetWidth which is the half of width to substract later while calculating left for the target i.e our box.
const offsetWidth = parseInt(getComputedStyle(target).width)/2;
// e.clientX gives us the x coordinate of the mouse pointer
// target.getBoundingClientRect().left gives us left position of the bounding rectangle and acts as a good offset to get the accurate left for our box.
target.style.left = `${e.clientX -target.getBoundingClientRect().left - offsetWidth}px`;
target.classList.add('remove');
setTimeout(() => { stage.removeChild(target) }, 2000);
}
}
stage.addEventListener('click', clickHandler);
}());
.stage {
overflow: hidden;
position: relative;
background: #eeeeaa;
width: 40vw;
height: 20vw;
}
#keyframes moving {
0% {
transform: translateX(0);
}
100% {
transform: translateX(30vw);
}
}
#keyframes remove {
0% {
transform: translate(0vh);
}
100% {
transform: translateY(60vw);
}
}
.character {
position: absolute;
width: 50px;
height: 50px;
background-repeat: no-repeat;
background-position: 50% 50%;
background-size: contain;
}
.moving{
animation: moving infinite alternate;
}
.remove {
animation: remove 0.2s cubic-bezier(.68,-0.55,.27,1.55) forwards;
}
.f0 {
background-color: black;
animation-duration: 2s;
}
.f1 {
left: 5%;
bottom: 5%;
animation-duration: 2s;
background-color: red;
}
<div class="stage">
<div class="character moving f1" data-id="1"></div>
</div>
Ok, maybe stackoverflow can help? :)
I'm trying, without any luck, to create a page transition effect with an svg image.
When the user clicks on a link in Page 1, a diamond shaped svg fades in like a portal into Page 2.
The basic idea is to recreate the space travel in the intro of the Alphaville - Forever Young video: https://www.youtube.com/watch?v=t1TcDHrkQYg
:)
Maybe the diamond also fades in from blue to transparent (but that is the next step).
Diamond svg: https://www.onlinewebfonts.com/icon/413
I suggest you use clip-path instead of a svg since animating an svg that big will be really slow and really laggy. You can change the clip path to show what you want. Bennet Feely created a nice generator that helps with this.
For the animation itself you can increase the width and height to fit your screen. Then fill the remainder by animating the Z axes.
Animation looks better in fullscreen then in the smaller preview
const links = document.querySelectorAll(".page-transition");
const overlay = document.querySelector(".overlay__diamond");
for(const link of links) {
link.addEventListener("click", (event) => {
event.preventDefault();
overlay.classList.add("overlay__diamond--animate");
setTimeout(() => window.location.reload(), 1000);
// This one is correct, one above is for the demo
// setTimeout(() => (window.location.href = link.href), 1000); // Same time as animation duration
});
}
.page {
background: green;
/* For demontrational purposes only, just to increase page size */
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.overlay {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
pointer-events: none;
perspective: 500px; /* Needed for translateZ to work */
}
.overlay__diamond {
width: 100%;
height: 100%;
background: blue;
animation: fadeout 1s linear forwards;
}
.overlay__diamond--animate {
animation: zoom 1s linear forwards;
clip-path: polygon(50% 0%, 75% 50%, 50% 100%, 25% 50%);
}
#keyframes fadeout {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
#keyframes zoom {
0% {
width: 0;
height: 0;
transform: translateZ(0);
}
100% {
width: 100%;
height: 100%;
transform: translateZ(400px); /* Can't go higher then the perspective */
}
}
<div class="page">
<!-- Replace #link with your actual urls -->
<a class="page-transition" href="#link">Link</a>
<a class="page-transition" href="#link">Link</a>
<a class="page-transition" href="#link">Link</a>
<div class="overlay">
<div class="overlay__diamond"></div>
</div>
</div>
I managed with the help of the ScrollMagic library to change my background img for my section three-section-container depending on scroll position. Additionally, I managed to add an overlay that will appear only when I am on a certain section of my page.
My issue now is that I would like to animate how background image changes (I want to come from right to left and stay positioned in the middle/ as you can see in code the background is changing 2 times). I tried with `transform: translateY(40px);
property in CSS but the result was inconsistent because the image would not be 100% of my screen. Also, I want my overlay to come from left to right, and I am quite confused how.
HTML
<div id="bigSection" class="three-section-container ">
<div id="target-overlay" class=" "></div>
<div class="sec1 " id="project01"></div>
<div class="sec2 " id="project02"></div>
<div class="sec3" id="project03"></div>
</div>
CSS
.three-section-container{
position: relative;
background-color: black;
transition: all 3s ease;
background-image: url('../../Assets/Images/graphic/last/poza-augmented-reality.png');
background-repeat: no-repeat;
background-color: black;
background-size: 100% 100vh;
background-attachment: fixed;
}
.fade-img1{
transition: all 1s ease;
background-image: url('../../Assets/Images/graphic/last/poza-augmented-reality.png');
background-repeat: no-repeat;
background-color: black;
background-size: 100% 100vh;
background-attachment: fixed;
// transform: translatey(20px);
opacity: 1;
margin: 0;
z-index: 999;
}
.fade-img2{
background-image: url('../../Assets/Images/graphic/last/2.png');
background-repeat: no-repeat;
background-color: black;
background-size: 100% 100vh;
background-attachment: fixed;
opacity: 1;
transition: all 1s ease;
margin: 0;
z-index: 999;
}
.fade-img3{
background-image: url('../../Assets/Images/graphic/last/poza-interior.png');
background-repeat: no-repeat;
background-color: black;
background-size: 100% 100vh;
background-attachment: fixed;
// transform: translateY(40px);
opacity: 1;
transition: all 0.3s ease;
margin: 0;
z-index: 999;
}
.sec1, .sec2, .sec3{
height: 100vh;
}
.overlay {
transition: 0.3s linear all;
position: absolute; /* Sit on top of the page content */
width: 40%; /* Full width (cover the whole page) */
height: 100%; /* Full height (cover the whole page) */
top: 0;
left: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.5); /* Black background with opacity */
z-index: 999; /* Specify a stack order in case you're using a different order for other elements */
}
JS
$(document).ready(function(){
var controller=new ScrollMagic.Controller()
// build a scene
var ourScene= new ScrollMagic.Scene({
triggerElement:'#project01',
duration:"100%"
})
.setClassToggle('#bigSection', 'fade-img1')
.addIndicators({
name:'fade scene',
colorTRigger:'black',
indent:200,
colorStart:'#75c695'
})
.addTo(controller)
var ourScene= new ScrollMagic.Scene({
triggerElement:'#project02',
duration:"100%"
})
.setClassToggle('#bigSection', 'fade-img2')
.addIndicators({
name:'fade scene',
colorTRigger:'black',
indent:200,
colorStart:'#75c695'
})
.addTo(controller)
var ourScene= new ScrollMagic.Scene({
triggerElement:'#project03',
duration:"200%"
})
.setClassToggle('#bigSection', 'fade-img3')
.addIndicators({
name:'fade scene',
colorTRigger:'black',
indent:200,
colorStart:'#75c695'
})
.addTo(controller)
var ourScene= new ScrollMagic.Scene({
triggerElement:'#project01',
// duration:"200%"
})
.setClassToggle('#target-overlay', 'overlay')
.addIndicators({
name:'overlay',
colorTRigger:'black',
indent:200,
colorStart:'#75c695'
})
.addTo(controller)
})
Any help is welcomed. Thank You
I'm not familiar with the ScrollMagic API but I think this code snippet can make things a little cleared from the JS and CSS prospective involved in the animation.
In fact most of them can be done without the need of externals API but just triggering back an forth a CSS class !
Hope this helps you a little bit:
let animationDone = false;
window.addEventListener("scroll", () => {
/*
* IF you scrolled more than a certain amount:
* in this case i choose half a page height's (50vh),
* you trigger the slide animation by adding the onscreen class to the background2 div.
* Otherwise if you previously triggered the animation and
* you scrolled in the opposite direction: the animation is triggered backwards.
*/
if(window.scrollY > window.innerHeight / 2) {
document.getElementsByClassName("background2")[0].classList.add("onscreen");
document.getElementById("secondPage").classList.add("onscreen");
animationDone = true; //We makes sure that we always know the state of our animation
} else if(animationDone) {
document.getElementsByClassName("background2")[0].classList.remove("onscreen");
document.getElementById("secondPage").classList.remove("onscreen");
animationDone = false; //We makes sure that we always know the state of our animation
}
}, {passive:true});
body {
color:white;
margin: 0;
width:100vw;
height:200vh; /* 200vh is only for demo purposes: it allows to scroll the html body even thought there's nothing inside */
}
#mainContent {
text-align: center;
z-index: 99;
position: absolute;
}
#mainContent > * {
display: flex;
justify-content: center;
align-items: center;
}
#firstPage {
width: 100vw;
height: 100vh;
}
#secondPage {
width: 100vw;
height: 100vh;
opacity: 0; /* This makes our background2 div transparent as soon as its hidden */
transform: translateX(-100vw); /* This shifts our background to the left by 100vw (its width) */
transition: 1s; /* The page content animation's duration */
}
#secondPage.onscreen {
/*
* This cancels the second page's previous shift (to left) when the onscreen class is applied to secondPage div
* in 0.3s so that it won't snap-> the left to right transition is realized !
*/
transform: translateY(0);
opacity: 1; /* This makes our background2 fades from transparent (its original state) to opaque */
}
.background1 {
z-index: 1; /* Lower stacking index than the other background to hide it */
position: fixed;
width: 100vw;
height: 100vh;
background: red;
}
.background2 {
z-index: 2; /* Higher stacking index than the other background to show it*/
position: fixed;
width: 100vw;
height: 100vh;
background: blue;
opacity: 0; /* This makes our background2 div transparent as soon as its hidden */
transform: translateX(100vw); /* This shifts our background to the right by 100vw (its width) */
transition: 0.3s; /* The background2 animation's duration */
}
.background2.onscreen {
/*
* This cancels the background's previous shift when the onscreen class is applied to background2
* in 0.3s so that it won't snap-> the right to left transition is realized !
*/
transform: translateY(0);
opacity: 1; /* This makes our background2 fades from transparent (its original state) to opaque */
}
<body>
<div id = "mainContent">
<h1 id = "firstPage">The main content goes here</h1>
<h1 id = "secondPage">Animation Triggered !</h1>
</div>
<div class = "background1"></div>
<div class = "background2"></div>
</div>
</body>
Maybe there is some javascript required please suggest.You can check at aditagarwal.com
CSS.
.images-wrapper{
position: fixed;
left: 0;
top: 80px;
bottom: 0;
width: 100%;
height: 100vh;
animation: animate 16s ease-in-out infinite;// maybe something here
background-size: cover;
}
#keyframes animate{
0%,100%{
background-image: url(coding2.jpeg);
}
25%{
background-image: url(Flowers.png);
}
50%{
background-image: url(Desert.png);
}
75%{
background-image: url(sunset.png);
}
}
HTML
<div class="images-wrapper">
</div>
As you didn't ask for a CSS only solution, I used some JS code here, which continuously changes the BG. Also check I added transition:background 1s ease-in which makes the transition smoother particularly.
const elem = document.getElementById("main");
const bg = [
"https://wallpapercave.com/wp/wp2599605.jpg",
"https://images.pexels.com/photos/257360/pexels-photo-257360.jpeg"
];
elem.style.backgroundImage = `url(${bg[0]})`;
let i = 0;
setInterval(() => {
i = i == 1 ? 0 : 1;
elem.style.backgroundImage = `url(${bg[i]})`;
}, 2000);
div {
position: fixed;
left: 0;
top: 80px;
bottom: 0;
width: 100%;
height: 100vh;
background-size: cover;
background-repeat:no-repeat;/*Modified for demo*/
transition: background 1s ease-in;
}
<div id="main">
</div>
If I use CSS without keyframes, insertRule works and everything is perfect. But if I use a CSS that contains a keyframe, I'll get the error:
Failed to execute 'insertRule' on 'CSSStyleSheet': Failed to parse the
rule ...
This works:
const css = window.document.styleSheets[0];
css.insertRule(`
div {
width: 100%;
animation: move 2s;
position: absolute;
transition: 0.65s;
}
`, css.cssRules.length);
This doesn't work:
const css = window.document.styleSheets[0];
css.insertRule(`
div {
width: 100%;
animation: move 2s;
position: absolute;
transition: 0.65s;
}
#keyframes move {
0% {
left: 0;
}
20% {
left: 100px;
}
100% {
left: -100%;
}
}
`, css.cssRules.length);
How can I make it work?
The posted code tries to insert two rules with a single call to insertRule.
Using two calls to insert a rule each
const css = window.document.styleSheets[0];
css.insertRule(`
div {
width: 100%;
animation: move 2s;
position: absolute;
transition: 0.65s;
}
`, css.cssRules.length);
css.insertRule(`
#keyframes move {
0% {
left: 0;
}
20% {
left: 100px;
}
100% {
left: -100%;
}
}
`, css.cssRules.length);
div { height: 3em;}
<div> DIV element</div>
works without throwing an error. Generating a syntax error if the rule parameter of insertRule contains more than one rule is documented on MDN in listed Restrictions.