Анимация новогодней ёлочки

1,00
р.
Наступает Новый год!
Хорошо бы поднять настроение себе и другим. Копировать и рассылать открыточки с красивыми картинками и гифками, скаченными из сети, уже как-то не интересно.
Хочется чего-то необычного, яркого, праздничного, которое есть только у тебя и которое можно подарить близкому человеку со словами,- "Я сделал это для тебя!" или оставить у себя с чувством удовлетворения и гордости :-)
Идея сделать новогодний конкурс анимаций принадлежит @Hamster
Вот и рисунок, который она дала для образца.

Смотрите, как и в любой другой новогодней открытке, тут есть потенциал для всевозможных анимаций.
Например:
Лучи от звезды.
Можно легко сделать с помощью stroke-dasharray для широкой строки. Добавить радиальный градиент, анимацию вращения строки и перемещения градиента.
Снежинки
Нарисовать в CSS или в SVG многие смогут и далее за анимировать размеры, вращение, падение снежинок.
Елочка Перемигивание гирлянд, легкое покачивание и кручение елочных игрушек, бенгальские огни и т.д и т.п Анимация фразы - С Новым 2019 годом! движение фразы и/или покачивание, обводка, появление букв Анимация новогодних персонажей,- белочки, зайчики :) Дед мороз и снегурочка
Можно делать весь вышеперечисленный список анимаций, можно реализовать один пункт или несколько.
Главное условие - картинки можно брать любые или рисовать свои, но анимация должна быть своя, никакого копи-паста!
Конкурс завершился.
Поздравляю победителя @Misha Saidov
Благодаря вашим работам дорогие участники, конкурс получился интересным, ярким, запоминающимся. Мы вместе узнали имена новых участников, которые сразу, с ходу ворвались в конкурс и сделали очень крутые работы. Все работы сделаны на высоком уровне и каждая по своему интересна и хороша.

Ответ
Это бандл из моих предыдущих ответов.
Как видите, код стал модульным - можно на его базе собрать любого Франкенштейна. Также код вариативный везде, где только можно - любой коэффициент можно поменять, чтобы добиться желаемого результата и резиновый (на сколько смог). Повторюсь, что я не умею в векторную графику и канвас, поэтому накодил все на дивах и картинках. Разверните на всю страницу, если сильно сплющит.
UPD 0: Добавил покачивание снежинок, изменил некоторые коэффициенты и сменил изображение звездочек елки на более нейтральное.
UPD 1: Добавил текст и заанимировал.
UPD 2: Санта! Он будет появляться и махать рукой с разных сторон экрана. Думаю, далее будут только какие-то минорные правки, а так - это финалочка )
UPD 3: Оптимизация. Теперь все делает один цикл (вместо 4-х, как раньше). Открытка стала чуть более снисходительна на старт: картинки появляются только после полной загрузки и убрал ненужный тут $(document).ready() Скрипт по-прежнему костыль :)


const randomBetween = (a, b) => { return (a + (Math.random() * (b - a))) } const randomArr = (arr) => { return arr[Math.round(Math.random() * (arr.length - 1))] } const params = { snowflakes: { amount: 15, duration: { min: 3, max: 8 }, size: { min: 10, max: 32 }, src: [ "http://pngimg.com/uploadsnowflakesnowflakes_PNG7578.png", "https://vignette.wikia.nocookie.net/fantendo/images/2/27/Snowflake.png/revision/latest?cb=20121220012520", "https://latxikadelacerveza.es/wp-content/plugins/christmas-panda/assets/imagesnowflake_5.png" ], container: "#snowflakes" }, tree: { amount: 300, stretching: 1.4, movement: 5, src: "https://i.imgur.com/pPjPR8q.png", duration: { min: 5, max: 50 }, size: { min: 20, max: 30 }, container: "#tree" }, rays: { amount: 40, duration: 30, width: 3, height: 100, perspective: 150, color: "rgba(215,249,111,1)", container: "#rays" }, text: { text: "С Новым 2019 годом!", duration: 30, width: 3, height: 100, perspective: 150, color: "rgba(215,249,111,1)", container: "#text" } } let totalAmount = 0 for (let key in params) { let amount = params[key].amount totalAmount += typeof amount === "number" ? amount : 0 } for (let i = 0 i < totalAmount i++) { if (i < params.snowflakes.amount) { let snowflake = $("") let randomSize = randomBetween(params.snowflakes.size.min, params.snowflakes.size.max) snowflake.css({ "width": randomSize + "px", "height": randomSize + "px", "left": randomBetween(0, 100) + "%", "animation-duration": randomBetween(params.snowflakes.duration.min, params.snowflakes.duration.max) + "s" }) $(params.snowflakes.container).append(snowflake) } if (i < params.tree.amount) { if (i === 0) { $(params.tree.container).append("") } let star = $("") let top = randomBetween(0, randomBetween(70, 100)) let randomSize = (randomBetween(params.tree.size.min, params.tree.size.max) * (top + 10) / 100) star.css({ "width": randomSize + "px", "height": randomSize + "px", "left": "calc(50% - " + (Math.sin(top / params.tree.stretching) * top) + "px)", "top": top + "%", "animation-duration": randomBetween(params.tree.duration.min, params.tree.duration.max) + "s", "transform-origin": (50 + randomBetween(-params.tree.movement, params.tree.movement)) + "% " + (50 + randomBetween(-params.tree.movement, params.tree.movement)) + "%" }) $(params.tree.container).append(star) } if (i < params.rays.amount) { if (i === 0) { $(params.rays.container).css({ "animation-duration": params.rays.duration + "s", width: params.rays.height * 2 + "px", height: params.rays.height * 2 + "px" }) window.alignRays = () => { let mainStar = $(params.tree.container).find(".main") $(params.rays.container).css("top", (mainStar.offset().top - params.rays.height) + (mainStar.height() / 2) + "px") } $(window).on("resize", window.alignRays) } let ray = $("
") ray.css({ "width": params.rays.width + "px", "height": params.rays.height + "px", "transform": "perspective(" + params.rays.perspective + "px) rotateZ(" + (360 / params.rays.amount * i) + "deg) rotateX(-60deg)", "background": "linear-gradient(to bottom, rgba(0,0,0,0) 0%," + params.rays.color + " 100%)" }) $(params.rays.container).append(ray) } if (i < params.text.text.length) { let char = params.text.text.substr(i, 1) char = char === " " ? "&nbsp " : char let charElem = $("
" + char + "
") charElem.css("animation-delay", (i / 10) + "s") $(params.text.container).append(charElem) } if (i === totalAmount - 1) { $("#root").css("display", "block") window.alignRays() let selector = [] for (let key in params) { let container = params[key].container selector.push(typeof container === "string" ? container + " > img" : "") } selector = selector.join(",") $(selector).css("display", "none") $(selector).on("load", (e) => $(e.target).css("display", "")) } } let santa = $("#santa") santa.css("display", "none") santa.on("load", (e) => $(e.target).css("display", "")) setInterval(() => { let side = ["right", "left"][Math.round(Math.random())] let randTop = Math.round(Math.random() * ($(window).height() - 220)) santa.css("top", randTop + "px") santa.addClass(side) setTimeout(() => { santa.removeClass() }, 2000) }, 4000) body, html { margin: 0 width: 100% height: 100% background: radial-gradient(ellipse at 50% 30%, #a1c920 10%, #1e2708 100%) overflow: hidden } #root { position: absolute width: 100% height: 100% overflow: hidden display: none } #santa { position: absolute display: none z-index: 12 top: 20px width: 135px animation-duration: 2s transform-origin: bottom animation-timing-function: linear } #santa.left { animation-name: santa-left display: block left: -135px } #santa.right { animation-name: santa-right display: block right: -135px } #text { position: absolute bottom: 8% left: 0 right: 0 margin: 0 auto z-index: 11 width: 390px white-space: nowrap } #text>div { position: relative display: inline-block float: left font-size: 40px font-family: Pacifico animation-name: text animation-iteration-count: infinite transform-origin: center center animation-timing-function: linear animation-duration: 4s color: white opacity: 0 text-shadow: 0 0 10px yellow } #rays { position: absolute top: 0 left: 0 right: 0 margin: 0 auto border-radius: 100% z-index: 8 animation-name: rotate transform-origin: center center animation-iteration-count: infinite animation-timing-function: linear background: radial-gradient(circle, rgba(0, 0, 0, 0.2) 0%, rgba(0, 212, 255, 0) 70%) } #rays>div { position: absolute top: 0 left: 0 right: 0 margin: 0 auto transform-origin: center bottom } #snowflakes { position: absolute top: 0 left: 0 width: 100% height: 100% z-index: 7 } #snowflakes>img { position: absolute animation-name: drop transform-origin: top center animation-iteration-count: infinite animation-timing-function: linear } #tree { position: absolute top: 20% left: 0 right: 0 bottom: 0 margin: 0 auto width: 200px height: 270px z-index: 9 } #tree>img.main { position: absolute top: -10px left: 0 right: 0 margin: 0 auto width: 35px height: 35px z-index: 1 animation-duration: 20s animation-name: rotate transform-origin: center center animation-iteration-count: infinite animation-timing-function: linear } #tree>img { position: absolute animation-name: shake transform-origin: center center animation-iteration-count: infinite animation-timing-function: ease-in-out } @keyframes drop { 0% { top: 0% opacity: 0 transform: rotate(0deg) } 20%, 80% { opacity: .7 } 50% { opacity: .5 } 100% { top: 100% opacity: 0 transform: rotate(360deg) } } @keyframes rotate { 0% { transform: rotate(0deg) } 100% { transform: rotate(360deg) } } @keyframes text { 0% { transform: rotate(-20deg) scale(0) opacity: 0 } 10% { transform: rotate(10deg) scale(1.1) opacity: 1 } 20% { transform: rotate(-5deg) scale(.9) opacity: 1 } 30% { transform: rotate(0deg) scale(1) opacity: 1 } 80% { transform: scale(1) opacity: 1 } 90% { transform: scale(2) opacity: 0 } 100% { transform: rotate(0deg) scale(1) opacity: 0 } } @keyframes shake { 0%, 100% { transform: rotate(0deg) scale(1) opacity: .4 } 10% { transform: rotate(50deg) scale(.5) opacity: .6 } 20% { transform: rotate(180deg) scale(.7) opacity: .7 } 30% { transform: rotate(150deg) scale(1) opacity: .4 } 40% { transform: rotate(130deg) scale(.6) opacity: .8 } 50% { transform: rotate(60deg) scale(0) opacity: 1 } 60% { transform: rotate(120deg) scale(.4) opacity: .8 } 70% { transform: rotate(300deg) scale(.1) opacity: .7 } 80% { transform: rotate(240deg) scale(.5) opacity: .6 } 90% { transform: rotate(200deg) scale(.9) opacity: .3 } } @keyframes santa-right { from, to { transform: translate3d(0, 0, 0) } 10%, 30%, 50%, 70%, 90% { transform: rotate(-75deg) } 20%, 40%, 60%, 80% { transform: rotate(-65deg) } 0%, 100% { transform: rotate(0deg) } } .santa-right { animation-name: santa-right } @keyframes santa-left { 10%, 30%, 50%, 70%, 90% { transform: rotate(75deg) } 20%, 40%, 60%, 80% { -webkit-transform: rotate(65deg) transform: rotate(65deg) } 0%, 100% { transform: rotate(0deg) } } .santa-left { -webkit-animation-name: santa-left animation-name: santa-left }