falbar Создание анимированного кольца загрузки при помощи SVG и GSAP

Создание анимированного кольца загрузки при помощи SVG и GSAP

14 октября 2018 Перевод 82 0

В этом уроке вы научитесь создавать анимацию значка загрузки с помощью SVG и GSAP - анимационная платформа от GreenSock. Эффект кольца загрузки опирается на очень полезный плагин - DrawSVGPlugin. Этот платный плагин способен помочь с созданием множества SVG анимаций и эффектов.

Реклама

Далее я во всех подробностях расскажу о создании колеса загрузки. Для этого я воспользуюсь TimelineMax от GSAP и DrawSVGPlugin. Последний нужен для того, чтобы постепенно открывать (или прятать) фрагменты векторной графики, что может сильно помочь в случае с эффектом загрузки.

Для начала советую ознакомиться со следующими инструментами:

  • GSAP (GreenSock Animation Platform);
  • TimelineMax;
  • DrawSVGPlugin.

Для некоторых читателей эти понятия уже не в новинку. На всякий случай для тех, кто все же с ними не знаком: GSAP - это набор JavaScript-библиотек, который может анимировать практически все.

Перейдем к делу. Мы попробуем создать значок загрузки. Это будет забавная анимация: полоска будет как бы выпрыгивать из какой-то жидкости и затем нырять туда снова. Почему бы и нет?

Для начала пропишем SVG в HTML:

<div id="container">
    <svg id="loader" width="200px" height="200px" viewBox="0 0 200 200">
        <path id="jump" fill="none" stroke="#2d2d2d" stroke-width="10" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M47.5,94.3c0-23.5,19.9-42.5,44.5-42.5s44.5,19,44.5,42.5" />
        <g stroke="#2d2d2d" stroke-width="1">
            <ellipse id="circleL" fill="none" stroke-miterlimit="10" cx="47.2" cy="95.6" rx="10.7" ry="2.7" />
            <ellipse id="circleR" fill="none" stroke-miterlimit="10" cx="136.2" cy="95.6" rx="10.7" ry="2.7" />
        </g>
    </svg>
</div>

Векторная графика была создана в Adobe Illustrator. То же самое можно сделать и в других приложениях: например, в Sketch (только для Mac) и Inkscape. Я нарисовал искривленную полоску и два круга на жидкости в Illustrator, как показано на картинке ниже. Вы можете заметить, что вокруг рисунка расположен большой красный прямоугольник. На это есть своя причина: когда копируешь графику из Illustrator на HTML-страницу, атрибуты viewBox для нее берутся из этого выделенного прямоугольника. То есть, если бы я выделил только полоску и рябь, в viewBox для графики были бы только суммарные широта и высота, а вокруг анимации бы не было никакого места. Не забывайте, что рябь будет "расплываться" за пределы холста, поэтому понадобится немного дополнительного пространства.

this-svg-was-created-using-adobe-illustrator

Чтобы получить более крупный холст (и чтобы рисунок находился в его центре), вместе с графикой нужно копировать и прямоугольник. Если мне понадобится SVG-холст размером 200х150, то нужно просто сделать прямоугольник 200х150, выделить его и всю остальную графику (в целом она занимает меньше пространства) и скопировать в HTML-файл. Затем можно просто удалить прямоугольник. Это просто небольшая хитрость, которую я использую, чтобы не получалось так, что элементы анимации обрезаются краями холста. В таком случае также не нужно скрупулезно менять все атрибуты так, чтобы все эти фрагменты оказались внутри холста.

Итак, у нас есть несколько элементов: выпрыгивающая полоска и 2 окружности, олицетворяющие рябь. Заметьте, что у всех элементов есть свой ID. Их можно создать в Illustrator, поменяв название слоя. Однако чаще всего получается так, что к ID добавляются какие-то другие символы, что немного сбивает с толку. Некоторые атрибуты, которые Illustrator добавляет в SVG-элементы, можно просто убрать, оставив только viewBox и другие параметры.

На этом HTML с SVG закончились.

Структура и макет

Создадим ссылки на все, что нам понадобится. Я всегда ввожу переменные, которые ссылаются на документы. В таком случае у вас в кэше уже будет необходимая ссылка, и коду не нужно будет обращаться к DOM каждый раз, когда вам захочется заставить элемент что-либо сделать. Однако будьте осторожны: это может занять много ценной памяти на телефонах, поэтому нужно учитывать требования ваших целевых устройств.

var container = document.getElementById('container');
var loader = document.getElementById('loader');
var circleL = document.getElementById('circleL');
var circleR = document.getElementById('circleR');
var jump = document.getElementById('jump');

Можно заметить, что у выпрыгивающей линии есть отражение, которое мы не стали рисовать. Просто продублируем линию и добавим к «loader», то есть сделаем ссылку на этот рисунок. Совсем скоро мы перевернем эту копию и уменьшим ее непрозрачность.

var jumpRef = jump.cloneNode();
loader.appendChild(jumpRef);

Далее нужно разместить рисунок по центру при помощи GSAP. Выровняем контейнер и графику внутри него при помощи TweenMax, потому что это нужно сделать лишь однажды и это не будет частью анимации. В CSS атрибуты xPercent и yPercent - то же самое, что trasform: translate(-50%;-50%), но с префиксами.

TweenMax.set([container, loader], {
    position: 'absolute',
    top: '50%',
    left: '50%',
    xPercent: -50,
    yPercent: -50
})

Наконец, воспользуемся TweenMax, чтобы перевернуть отражение полоски. Поменяем transformOrigin на 110% по оси Y и поставим отрицательное значение scaleY. В transformOrigin можно было бы поставить и 100%, но тогда отражение находится слишком близко к оригиналу.

TweenMax.set(jumpRef, {
    transformOrigin: '50% 110%',
    scaleY: -1,
    alpha: 0.05
})

Время анимации!

Теперь мы готовы начать анимировать с помощью TimelineMax. Нам очень поможет его возможность связывать анимированные элементы. Об этом мы еще поговорим позже.

Сначала сделаем ссылку на новую копию TimelineMax. Это тоже объект, в котором можно устанавливать различные свойства и обработчики событий, такие как delay, yoyo, onComplete и так далее. В данном случае мы сделаем так, чтобы TimelineMax повторялся вечно, присвоив значение -1.

var myTimeline = new TimelineMax({
    repeat: -1
})

Теперь начнем добавлять твины на временную шкалу. Как упоминалось ранее, твины TimelineMax/Lite могут связываться между собой, то есть не нужно будет делать копию переменной каждый раз, когда вы добавляете твин. Это нужно сделать только единожды в самом начале. Только не помещайте между твинами точку с запятой.

Сначала используем DrawSVGPlugin, чтобы и выныривающая полоска, и ее отражение исчезли. Здесь разделенное пробелами значение определяет сегмент. «0% 0%» означает, что и начало, и конец фрагмента принимают значение 0%. Теперь можно начать отрисовывать их с левой стороны. Обратите внимание, что можно анимировать обе линии одновременно, передав их TimelineMax в виде массива.

Также сделаем так, чтобы радиус кругов-ряби по осям X и Y был 0 (то есть пренебрежимо мал) при помощи конструкции «attr», которая встроена в TweenMax и сделана для анимирования. Важно: после первого упоминания set не используется точка с запятой, и поэтому следующие упоминания связываются с первым. В этом случае не нужно вновь делать копию myTimeline.

myTimeline.set([jump, jumpRef], {
    drawSVG: '0% 0%'
}).set([circleL, circleR], {
    attr: {
        rx: 0,
        ry: 0,
    }
})

Сейчас мы только присваиваем значения, а сам процесс анимации еще не начался. Временная шкала будет повторяться вечно, поэтому каждый раз, когда она начинает проигрываться сначала, они будут использоваться для инициализации состояний элементов. Пока что длина анимации - 0.

Теперь мы будем вызывать to - а это уже настоящая анимация. Ура!

Обе линии начинают появляться на 30% своей длины. Значение drawSVG «0% 30%», что фрагмент начнется на 0%, а закончится на 30% от всей длины. На изображении ниже показано, как это выглядит. Мы также используем linear.ease, чтобы анимация была гладкой и как бы рисовалась.

progressive-smooth-draw-effect
.to([jump, jumpRef], 0.4, {
    drawSVG: '0% 30%',
    ease: Linear.easeNone
})

Теперь сделаем так, чтобы левый кружок, олицетворяющий рябь, стал увеличиваться. Для этого анимируем его rx и ry атрибуты. Если бы у них обоих было бы одинаковое значение «+=30», тогда был бы ровный круг. Однако окружности под действием перспективы выглядят иначе. Именно поэтому значение rx должно быть больше ry. Тогда у нас получится овал. Этот фрагмент автоматически добавляется в конец временной шкалы (на 0,4 с, потому что именно столько сейчас длится анимация).

В конце был добавлен еще один ни с чем не связанный параметр («-=0.1»). Это значит, что этот кусок анимации будет воспроизводиться на 0,3 с (0,4-0,1). Благодаря анимации как бы смешаются между собой и не будут выглядеть слишком искусственно.

.to(circleL, 2, {
    attr: {
        rx: '+=30',
        ry: '+=10'
    },
    alpha: 0,
    ease: Power1.easeOut
}, '-=0.1')

Продолжим анимировать полоски. Теперь они движутся вперед, и за ними поспевают их концы. Мы вновь добавляем этот фрагмент в конец анимации, затем двигаем его немного назад по временной шкале (на 1,9 с). Теперь значение drawSVG – «50% 80%», то есть 30% полоски все еще остаются невидимы (начало - на 50% длины, конец - на 80%).

.to([jump, jumpRef], 1, {
    drawSVG: '50% 80%',
    ease: Linear.easeNone
}, '-=1.9')

Последнее действие для полосок. Теперь мы анимируем их до конца. Значение drawSVG стало «100% 100%», то есть начало и конец фрагмента совпадают. Мы также заставили этот момент наступить раньше - на 0,7 с.

.to([jump, jumpRef], 0.7, {
    drawSVG: '100% 100%',
    ease: Linear.easeNone
}, '-=0.9')

Не забываем про кружок-рябь, который находится справа. Он сначала увеличивается до 30 по оси X и 10 по оси Y, как и его левая копия, затем исчезает.

.to(circleR, 2, {
    attr: {
        rx: '+=30',
        ry: '+=10'
    },
    alpha: 0,
    ease: Power1.easeOut
}, '-=.5');

myTimeline.timeScale(3);

При запуске этой анимации она будет воспроизводиться вечно. Когда я сделал эту работу, я долгое время подгонял не связанные ни с чем параметры, чтобы моменты, когда полоска выпрыгивает и когда начинается рябь, выглядели натурально. Когда же я наконец закончил, то понял, что анимация двигается слишком медленно (хорошо хоть движение было равномерным). Чтобы ускорить движение, я добавил эту строчку:

myTimeline.timescale(3);

Полный код

Ниже представлен получившийся код:

var container = document.getElementById('container');
var loader = document.getElementById('loader');
var circleL = document.getElementById('circleL');
var circleR = document.getElementById('circleR');
var jump = document.getElementById('jump');
var jumpRef = jump.cloneNode();

loader.appendChild(jumpRef);

TweenMax.set([container, loader], {
    position: 'absolute',
    top: '50%',
    left: '50%',
    xPercent: -50,
    yPercent: -50
})

TweenMax.set(jumpRef, {
    transformOrigin: '50% 110%',
    scaleY: -1,
    alpha: 0.05
})

var tl = new TimelineMax({
    repeat: -1,
    yoyo: false
});

tl.timeScale(3);

tl.set([jump, jumpRef], {
    drawSVG: '0% 0%'
})
.set([circleL, circleR], {
    attr: {
        rx: 0,
        ry: 0,
    }
})
.to([jump, jumpRef], 0.4, {
    drawSVG: '0% 30%',
    ease: Linear.easeNone
})
.to(circleL, 2, {
    attr: {
        rx: '+=30',
        ry: '+=10'
    },
    alpha: 0,
    ease: Power1.easeOut
}, '-=0.1')
.to([jump, jumpRef], 1, {
    drawSVG: '50% 80%',
    ease: Linear.easeNone
}, '-=1.9')
.to([jump, jumpRef], 0.7, {
    drawSVG: '100% 100%',
    ease: Linear.easeNone
}, '-=0.9')
.to(circleR, 2, {
    attr: {
        rx: '+=30',
        ry: '+=10'
    },
    alpha: 0,
    ease: Power1.easeOut
}, '-=.5')

Вот и все. Сначала мы отрисовали необходимую графику в Illustrator (и дали слоям названия, которые соответствуют их ID). Далее мы выбрали все элементы и перенесли их на HTML-страницу, где немного подчистили ID и ненужные атрибуты, которые прописывает Illustrator. Затем мы дублировали выпрыгивающую линию, чтобы получить отражение, а с помощью TweenMax, TimelineMax и DrawSVGPlugin сделали копию TimelineMax и разбили на промежуточные кадры появление линий и расширение кружков-ряби. После этого пришлось очень долго подбирать время, пока работа не станет выглядеть естественно. Уже потом мы поняли, что действие развивается слишком медленно, и ускорили его. Так получилась наша финальная анимация.

Анимированные линии - популярный эффект в видеографике и веб-разработках. Теперь вы можете легко и просто создать его самостоятельно, используя GSAP.

Реклама
Комментариев еще не оставлено
no_avatar