В одном из проектов заказчик попросил сделать реалистичную анимацию ёлочных шаров, которые начинали бы раскачиваться при касании мышью. Вот что из этого получилось.
Проведите мышкой по шарам, и они начнут качаться со все уменьшающейся амплитудой, пока не остановятся через некоторое время.
.ny-2018-wrapper {
position: relative;
overflow: hidden;
}
.ny-2018-balls {
position: absolute;
right: 0;
top: 0;
height: 50%;
width: 25%;
z-index: 1;
-webkit-animation: slide-in-from-top 1s ease-in-out forwards;
-moz-animation: slide-in-from-top 1s ease-in-out forwards;
-o-animation: slide-in-from-top 1s ease-in-out forwards;
animation: slide-in-from-top 1s ease-in-out forwards;
}
.ny-2018-balls img {
cursor: pointer;
height: 100%;
position: absolute;
}
.ball {
transform-origin: center top;
}
.glow {
transform-origin: center center;
pointer-events: none;
-webkit-animation: glow 1.67s ease-in-out infinite;
-moz-animation: glow 1.67s ease-in-out infinite;
-o-animation: glow 1.67s ease-in-out infinite;
animation: glow 1.67s ease-in-out infinite;
}
#ball1 {
left: 0;
top: 9%;
height: 46%;
}
#ball2 {
left: 15%;
top: 3%;
height: 97%;
}
#ball3 {
left: 31%;
top: 0;
height: 71%;
}
#ball4 {
left: 51%;
top: 13%;
height: 87%;
}
#ball5 {
left: 72%;
top: 8%;
height: 61%;
}
#glow1 {
height: 14%;
left: -1.1%;
top: 42%;
transform-origin: center -231%;
opacity: 0;
animation-delay: 0.2s;
}
#glow2 {
height: 18.5%;
left: 13.5%;
top: 82.8%;
transform-origin: center -438%;
opacity: 0;
animation-delay: 0s;
}
#glow3 {
height: 23%;
left: 29.1%;
top: 49.6%;
transform-origin: center -213%;
opacity: 0;
animation-delay: 0.4s;
}
#glow4 {
height: 25.7%;
left: 49.3%;
top: 75.7%;
transform-origin: center -244%;
opacity: 0;
animation-delay: 1s;
}
#glow5 {
height: 28%;
left: 70%;
top: 43%;
transform-origin: center -124%;
opacity: 0;
animation-delay: 0.7s;
}
@-webkit-keyframes slide-in-from-top {
0% {
transform: translateY(-100%);
}
100% {
transform: translateY(0);
}
}
@-moz-keyframes slide-in-from-top {
0% {
transform: translateY(-100%);
}
100% {
transform: translateY(0);
}
}
@-o-keyframes slide-in-from-top {
0% {
transform: translateY(-100%);
}
100% {
transform: translateY(0);
}
}
@keyframes slide-in-from-top {
0% {
transform: translateY(-100%);
}
100% {
transform: translateY(0);
}
}
@-webkit-keyframes glow {
0% {
opacity: 0;
}
50% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@-moz-keyframes glow {
0% {
opacity: 0;
}
50% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@-o-keyframes glow {
0% {
opacity: 0;
}
50% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@keyframes glow {
0% {
opacity: 0;
}
50% {
opacity: 1;
}
100% {
opacity: 0;
}
}
jQuery(
function( $ ) {
// Анимация шариков
function calcTransform( arrParams, percent ) {
var valS = 0;
var valR = 0;
var valO = 0;
var length = arrParams.length;
for ( var i = 0; i 100 ) {
continue;
}
var cycles = 3;
var attenuation = 0.025;
var sin = Math.sin( time / 100 * 360 * Math.PI / 180 * cycles );
var amplitude = sin * Math.exp( — attenuation * time );
valS += amplitude * ( params.maxScale — 1 );
valR += amplitude * params.maxAngle;
var sin_90 = Math.sin( time / 100 * 360 * Math.PI / 180 * cycles — 90 * Math.PI / 180 );
valO = ( sin_90 + 1 ) / 2;
}
valO = valO / length;
valS = 1 + valS;
return {
transform: ‘scale(‘ + valS + ‘) rotate(‘ + valR + ‘deg)’,
opacity: valO
};
}
$( ‘.ny-2018-balls .ball’ ).hover(
function() {
if ( ! $( this ).hasClass( ‘deaf’ ) ) {
$( this ).addClass( ‘deaf’ );
var params = {
init: 0,
duration: 5000 + ( Math.random() * 2 — 1 ) * 2000,
maxScale: 1 + ( Math.random() * 2 — 1 ) * 0.25,
maxAngle: 10 * ( Math.random() * 2 — 1 )
};
var arrParams = $( this ).data( ‘arrParams’ );
if ( typeof arrParams === ‘undefined’ ) {
arrParams = [];
} else {
var currentPercent = parseInt( $( this ).css( ‘border-spacing’ ) );
var length = arrParams.length;
for ( var i = 0; i 100 ) {
arrParams[i].init = 100;
}
}
arrParams[length — 1].init = currentPercent;
}
arrParams.push( params );
$( this ).data( ‘arrParams’, arrParams );
if ( ! $( this ).hasClass( ‘animating’ ) ) {
$( this ).addClass( ‘animating’ );
$( this ).dequeue().stop().animate(
{
// fake property, just to call step function
borderSpacing: 100 // must be 100, used as % of animation completed
},
{
step: function( now, fx ) {
var angle = now;
var percent = now / Math.abs( fx.end — fx.start );
var values = calcTransform( arrParams, percent );
$( this ).css( ‘-webkit-transform’, values.transform );
$( this ).css( ‘-ms-transform’, values.transform );
$( this ).css( ‘transform’, values.transform );
$( this ).next().css( ‘-webkit-transform’, values.transform );
$( this ).next().css( ‘-ms-transform’, values.transform );
$( this ).next().css( ‘transform’, values.transform );
},
duration: params.duration,
easing: ‘linear’,
complete: function() {
$( this ).removeClass( ‘animating’ ).dequeue();
$( this ).removeClass( ‘deaf’ );
$( this ).removeData( ‘arrParams’ );
$( this ).css( ‘border-spacing’, 0 );
}
}
);
}
}
}
);
}
);
JS-код оказался не слишком простым в реализации. Возможно, кому-то пригодятся заложенные в него идеи.
При наведении указателя мыши на шар инициализируется длительность раскачивания (от 3 до 7 секунд) и амплитуды раскачивания в двух плоскостях — плоскости экрана (maxAngle) и перпендикулярной плоскости (maxScale).
var params = { init: 0, duration: 5000 + ( Math.random() * 2 - 1 ) * 2000, maxScale: 1 + ( Math.random() * 2 - 1 ) * 0.25, maxAngle: 10 * ( Math.random() * 2 - 1 ) };
Вычисление положения на экране производится по формуле затухающего математического маятника:
function calcTransform( arrParams, percent ) { var valS = 0; var valR = 0; var valO = 0; var length = arrParams.length; for ( var i = 0; i < length; i ++ ) { var params = arrParams[i]; var time = ( params.init + percent ) * 100; if ( time > 100 ) { continue; } var cycles = 3; var attenuation = 0.025; var sin = Math.sin( time / 100 * 360 * Math.PI / 180 * cycles ); var amplitude = sin * Math.exp( - attenuation * time ); valS += amplitude * ( params.maxScale - 1 ); valR += amplitude * params.maxAngle; var sin_90 = Math.sin( time / 100 * 360 * Math.PI / 180 * cycles - 90 * Math.PI / 180 ); valO = ( sin_90 + 1 ) / 2; } valO = valO / length; valS = 1 + valS; return { transform: 'scale(' + valS + ') rotate(' + valR + 'deg)', opacity: valO }; }
Полный html, css, js код приведён ниже:
<style> .ny-2018-wrapper { position: relative; } .ny-2018-balls { position: absolute; right: 0; top: 0; height: 50vh; width: 44vh; z-index: 1; -webkit-animation: slide-in-from-top 1s ease-in-out forwards; -moz-animation: slide-in-from-top 1s ease-in-out forwards; -o-animation: slide-in-from-top 1s ease-in-out forwards; animation: slide-in-from-top 1s ease-in-out forwards; } .ny-2018-balls img { cursor: pointer; height: 100%; position: absolute; } .ball { transform-origin: center top; } .glow { transform-origin: center center; pointer-events: none; -webkit-animation: glow 1.67s ease-in-out infinite; -moz-animation: glow 1.67s ease-in-out infinite; -o-animation: glow 1.67s ease-in-out infinite; animation: glow 1.67s ease-in-out infinite; } #ball1 { left: 0; top: 9%; height: 46%; } #ball2 { left: 15%; top: 3%; height: 97%; } #ball3 { left: 31%; top: 0; height: 71%; } #ball4 { left: 51%; top: 13%; height: 87%; } #ball5 { left: 72%; top: 8%; height: 61%; } #glow1 { height: 14%; left: -1.1%; top: 42%; transform-origin: center -231%; opacity: 0; animation-delay: 0.2s; } #glow2 { height: 18.5%; left: 13.5%; top: 82.8%; transform-origin: center -438%; opacity: 0; animation-delay: 0s; } #glow3 { height: 23%; left: 29.1%; top: 49.6%; transform-origin: center -213%; opacity: 0; animation-delay: 0.4s; } #glow4 { height: 25.7%; left: 49.3%; top: 75.7%; transform-origin: center -244%; opacity: 0; animation-delay: 1s; } #glow5 { height: 28%; left: 70%; top: 43%; transform-origin: center -124%; opacity: 0; animation-delay: 0.7s; } @-webkit-keyframes slide-in-from-top { 0% { transform: translateY(-100%); } 100% { transform: translateY(0); } } @-moz-keyframes slide-in-from-top { 0% { transform: translateY(-100%); } 100% { transform: translateY(0); } } @-o-keyframes slide-in-from-top { 0% { transform: translateY(-100%); } 100% { transform: translateY(0); } } @keyframes slide-in-from-top { 0% { transform: translateY(-100%); } 100% { transform: translateY(0); } } @-webkit-keyframes glow { 0% { opacity: 0; } 50% { opacity: 1; } 100% { opacity: 0; } } @-moz-keyframes glow { 0% { opacity: 0; } 50% { opacity: 1; } 100% { opacity: 0; } } @-o-keyframes glow { 0% { opacity: 0; } 50% { opacity: 1; } 100% { opacity: 0; } } @keyframes glow { 0% { opacity: 0; } 50% { opacity: 1; } 100% { opacity: 0; } } </style> <script> jQuery( function( $ ) { // Анимация шариков function calcTransform( arrParams, percent ) { var valS = 0; var valR = 0; var valO = 0; var length = arrParams.length; for ( var i = 0; i < length; i ++ ) { var params = arrParams[i]; var time = ( params.init + percent ) * 100; if ( time > 100 ) { continue; } var cycles = 3; var attenuation = 0.025; var sin = Math.sin( time / 100 * 360 * Math.PI / 180 * cycles ); var amplitude = sin * Math.exp( - attenuation * time ); valS += amplitude * ( params.maxScale - 1 ); valR += amplitude * params.maxAngle; var sin_90 = Math.sin( time / 100 * 360 * Math.PI / 180 * cycles - 90 * Math.PI / 180 ); valO = ( sin_90 + 1 ) / 2; } valO = valO / length; valS = 1 + valS; return { transform: 'scale(' + valS + ') rotate(' + valR + 'deg)', opacity: valO }; } $( '.ny-2018-balls .ball' ).hover( function() { if ( ! $( this ).hasClass( 'deaf' ) ) { $( this ).addClass( 'deaf' ); var params = { init: 0, duration: 5000 + + ( Math.random() * 2 - 1 ) * 2000, maxScale: 1 + ( Math.random() * 2 - 1 ) * 0.25, maxAngle: 10 * ( Math.random() * 2 - 1 ) }; var arrParams = $( this ).data( 'arrParams' ); if ( typeof arrParams === 'undefined' ) { arrParams = []; } else { var currentPercent = parseInt( $( this ).css( 'border-spacing' ) ); var length = arrParams.length; for ( var i = 0; i < length - 1; i ++ ) { arrParams[i].init += currentPercent; if ( arrParams[i].init > 100 ) { arrParams[i].init = 100; } } arrParams[length - 1].init = currentPercent; } arrParams.push( params ); $( this ).data( 'arrParams', arrParams ); if ( ! $( this ).hasClass( 'animating' ) ) { $( this ).addClass( 'animating' ); $( this ).dequeue().stop().animate( { // fake property, just to call step function borderSpacing: 100 // must be 100, used as % of animation completed }, { step: function( now, fx ) { var angle = now; var percent = now / Math.abs( fx.end - fx.start ); var values = calcTransform( arrParams, percent ); $( this ).css( '-webkit-transform', values.transform ); $( this ).css( '-ms-transform', values.transform ); $( this ).css( 'transform', values.transform ); $( this ).next().css( '-webkit-transform', values.transform ); $( this ).next().css( '-ms-transform', values.transform ); $( this ).next().css( 'transform', values.transform ); }, duration: params.duration, easing: 'linear', complete: function() { $( this ).removeClass( 'animating' ).dequeue(); $( this ).removeClass( 'deaf' ); $( this ).removeData( 'arrParams' ); $( this ).css( 'border-spacing', 0 ); } } ); } } } ); } ); </script> <div class="ny-2018-wrapper"> <img src="https://wordpressify.ru/wp-content/uploads/2021/03/xmas-landscape.jpg" alt="XMas Landscape" srcset="https://kagg.eu/wp-content/uploads/2019/01/xmas-landscape-960x541.jpg 960w, https://kagg.eu/wp-content/uploads/2019/01/xmas-landscape-200x113.jpg 200w, https://kagg.eu/wp-content/uploads/2019/01/xmas-landscape-540x304.jpg 540w, https://kagg.eu/wp-content/uploads/2019/01/xmas-landscape-768x433.jpg 768w, https://wordpressify.ru/wp-content/uploads/2021/03/xmas-landscape.jpg 1005w" sizes="(max-width: 1005px) 100vw, 1005px"> <div class="ny-2018-balls"> <img class="ball" id="ball1" alt="ball1" src="https://wordpressify.ru/wp-content/uploads/2021/03/ball1.png"> <img class="glow" id="glow1" alt="glow1" src="https://wordpressify.ru/wp-content/uploads/2021/03/glow1.png"> <img class="ball" id="ball2" alt="ball2" src="https://wordpressify.ru/wp-content/uploads/2021/03/ball2.png"> <img class="glow" id="glow2" alt="glow2" src="https://wordpressify.ru/wp-content/uploads/2021/03/glow2.png"> <img class="ball" id="ball3" alt="ball3" src="https://wordpressify.ru/wp-content/uploads/2021/03/ball3.png"> <img class="glow" id="glow3" alt="glow3" src="https://wordpressify.ru/wp-content/uploads/2021/03/glow3.png"> <img class="ball" id="ball4" alt="ball4" src="https://wordpressify.ru/wp-content/uploads/2021/03/ball4.png"> <img class="glow" id="glow4" alt="glow4" src="https://wordpressify.ru/wp-content/uploads/2021/03/glow4.png"> <img class="ball" id="ball5" alt="ball5" src="https://wordpressify.ru/wp-content/uploads/2021/03/ball5.png"> <img class="glow" id="glow5" alt="glow5" src="https://wordpressify.ru/wp-content/uploads/2021/03/glow5.png"> </div> </div>
Источник: KAGG Design