Reputation: 321
I have two animations called slide
and bounce-return
which both should be playing with 0 delay and 1s duration. Due to what I suspect is some kind of race condition in animation processing logic, these animations are out of sync by 1 frame regardless of animation speed or refresh rate. How can I stop these circles from jumping in and out?
(If you don't see any problem right away, view the snippet in full page or open the developer tools.)
.items {
--var-circle-size: 16px;
--var-circle-space: 8px;
--var-circle-border: solid red;
--var-circle-border-width: 0px;
--var-circle-shadow: 3px 2px 4px -1px rgba(0, 0, 0, 0.4);
--var-circle-shadow-high: 6px 4px 6px -2px rgba(0, 0, 0, 0.2);
--var-circle-count: 5;
--var-anim-bounce-func: cubic-bezier(0.7, 0.1, 0.6, 1.6);
--var-anim-time-unit: calc(1s);
--var-anim-color-cycle-time: calc(var(--var-circle-count)*var(--var-anim-time-unit));
--var-anim-color-cycle-func: steps(1);
--var-anim-color-1: #6db;
--var-anim-color-2: #be4;
--var-anim-color-3: #ec5;
--var-anim-color-4: #d86;
--var-anim-color-5: #f8c;
--var-anim-slide-time: var(--var-anim-time-unit);
--var-anim-slide-func: linear;
--var-circle-size-space: calc(var(--var-circle-size) + var(--var-circle-space));
--var-anim-slide-amount: calc(var(--var-circle-size-space)/2);
--var-anim-slide-start: calc(-1*var(--var-anim-slide-amount));
--var-anim-slide-end: var(--var-anim-slide-amount);
}
.maximized {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.container {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.items {
font-size: 0;
white-space: nowrap;
user-select: none;
pointer-events: none;
margin-right: calc(-1*var(--var-circle-space) - var(--var-circle-size-space));
animation: slide var(--var-anim-slide-time) var(--var-anim-slide-func) infinite;
}
.circle {
width: var(--var-circle-size);
height: var(--var-circle-size);
border-radius: 100%;
box-shadow: var(--var-circle-shadow);
display: inline-block;
margin: 0;
margin-right: var(--var-circle-space);
box-sizing: border-box;
border: var(--var-circle-border);
border-width: min(var(--var-circle-border-width), calc(var(--var-circle-size)/2));
background-color: var(--var-anim-bgcolor);
--var-color-cycle-anim: anim-color-cycle var(--var-anim-color-cycle-time) var(--var-anim-color-cycle-func) calc(var(--var-anim-delay-fact)*var(--var-anim-time-unit)) infinite;
animation: var(--var-color-cycle-anim);
}
@keyframes anim-color-cycle {
0% {
--var-anim-bgcolor: var(--var-anim-color-1);
}
20% {
--var-anim-bgcolor: var(--var-anim-color-2);
}
40% {
--var-anim-bgcolor: var(--var-anim-color-3);
}
60% {
--var-anim-bgcolor: var(--var-anim-color-4);
}
80% {
--var-anim-bgcolor: var(--var-anim-color-5);
}
100% {
--var-anim-bgcolor: var(--var-anim-color-1);
}
}
@keyframes slide {
0% {
transform: translate(var(--var-anim-slide-start), 0);
}
100% {
transform: translate(var(--var-anim-slide-end), 0);
}
}
@keyframes bounce-inout {
40%,
60% {
transform: translate(0, var(--var-anim-bounce-inout-fact));
box-shadow: var(--var-circle-shadow-high);
}
}
@keyframes bounce-return {
10% {
left: 0;
}
90%,
100% {
left: var(--var-anim-bounce-return-amount);
}
}
@keyframes bounce-inout-fact-alternate {
0% {
--var-anim-bounce-inout-fact: calc(-1*var(--var-circle-size-space));
}
50% {
--var-anim-bounce-inout-fact: var(--var-circle-size-space);
}
}
.circle:nth-child(1) {
--var-anim-delay-fact: -4;
}
.circle:nth-child(2) {
--var-anim-delay-fact: -3;
}
.circle:nth-child(3) {
--var-anim-delay-fact: -2;
}
.circle:nth-child(4) {
--var-anim-delay-fact: -1;
}
.circle:nth-child(5) {
--var-anim-delay-fact: -0;
}
.circle:last-child {
--var-anim-bounce-inout-fact: calc(-1*var(--var-circle-size-space));
--var-anim-bounce-return-amount: calc(-1 * var(--var-circle-count) * var(--var-circle-size-space));
position: relative;
animation: var(--var-color-cycle-anim), bounce-inout var(--var-anim-time-unit) var(--var-anim-bounce-func) infinite, bounce-return var(--var-anim-time-unit) ease-in-out infinite, bounce-inout-fact-alternate calc(2*var(--var-anim-time-unit)) steps(1) infinite;
}
<div class="maximized container">
<div class="items">
<div class="circle"></div>
<div class="circle"></div>
<div class="circle"></div>
<div class="circle"></div>
<div class="circle"></div>
</div>
</div>
Upvotes: 0
Views: 182
Reputation: 36492
I have a theory that combining everything into one set of keyframes for each circle, rather than try to synchronise an animation on items with the circle animations, might help remove the problem with phasing.
To test it here's a snippet that roughly reproduces the action of the given code. In it the items div is not animated. Each circle keeps its original color and is animated to the right, up, back, to the right, down, back - with little bounces at each end.
No 'flashing/shadowing' has been observed. Also the amount of processing being done is less.
On the code in the question my laptop was showing an average of around 5% cpu and 15% gpu usage. On this snippet they are around 1.5% and 11.5% respectively.
.items {
--size: 16px;
--space: 8px;
--border: solid red;
--border-width: 0px;
--shadow: 3px 2px 4px -1px rgba(0, 0, 0, 0.4);
--shadow-high: 6px 4px 6px -2px rgba(0, 0, 0, 0.2);
--count: 5;
--bounce-func: cubic-bezier(0.7, 0.1, 0.6, 1.6);
--time-unit: calc(1s);
--cycle-time: calc(2 * var(--count)*var(--time-unit));
--color-cycle-func: steps(1);
--color-1: #6db;
--color-2: #be4;
--color-3: #ec5;
--color-4: #d86;
--color-5: #f8c;
--slide-time: var(--time-unit);
--slide-func: linear;
}
.maximized {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.container {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.items {
font-size: 0;
white-space: nowrap;
user-select: none;
pointer-events: none;
width: calc((var(--size) + var(--space)) * var(--count));
height: var(--size);
position: relative;
}
.circle {
position: absolute;
width: var(--size);
height: var(--size);
border-radius: 100%;
box-shadow: var(--shadow);
display: inline-block;
margin: 0;
margin-right: var(--space);
box-sizing: border-box;
border: var(--border);
border-width: min(var(--border-width), calc(var(--size)/2));
animation: bounceround infinite var(--cycle-time) var(--slide-func);
animation-delay: var(--delay);
background-color: var(--color);
}
.circle:nth-child(1) {
--color: var(--color-1);
--delay: -8s;
}
.circle:nth-child(2) {
--color: var(--color-2);
--delay: -6s;
}
.circle:nth-child(3) {
--color: var(--color-3);
--delay: -4s;
}
.circle:nth-child(4) {
--color: var(--color-4);
--delay: -2s;
}
.circle:nth-child(5) {
--color: var(--color-5);
--delay: 0s;
}
@keyframes bounceround {
0% {
}
36.6666% {
transform: translateX(calc(var(--count) * 100%)); /* move to the right */
}
40% {
transform: translateX(calc((var(--count) - 0.5) * 100%)) translateY(calc(-100% - var(--size))); /* go up a bit */
}
41% {
transform: translateX(calc((var(--count) - 1) * 100%)) translateY(calc(-100% - (var(--size) / 2))); /* come down a fraction */
}
45% {
transform: translateX(0) translateY(-100%); /* move to the left */
}
49% {
transform: translateX(0) translateY(calc(var(--size) / 4)); /* jump down a bit*/
}
50% {
transform: translateX(0) translateY(0); /* go down to start position */
}
86.6666% {
transform: translateX(calc(var(--count) * 100%)); /* move to the right */
}
90% {
transform: translateX(calc((var(--count) - 0.5) * 100%)) translateY(calc(100% + var(--size))); /* go down a bit */
}
91% {
transform: translateX(calc((var(--count) - 1) * 100%)) translateY(calc(100% + calc(var(--size) / 2))); /* go up a fraction */
}
95% {
transform: translateX(0) translateY(100%); /* move to the left */
}
99% {
transform: translateX(0) translateY(calc(-1 * var(--size) / 4)); /* jump up a bit */
}
100% {
transform: translateX(0) translateY(0); /* back to where we started */
}
}
<div class="maximized container">
<div class="items">
<div class="circle"></div>
<div class="circle"></div>
<div class="circle"></div>
<div class="circle"></div>
<div class="circle"></div>
</div>
</div>
Upvotes: 1
Reputation: 321
Possible workaround: Add duplicate version of circles just with color-cycle
and no moving animation and have them overlap with original one, and swap visibility of it for just a few millis when the animation iteration is about to restart.
Upvotes: 0