Самый простой способ создавать облака
Облака очень часто используются и в виде иконок и как отдельный элемент фона, фоновой анимации и во многих других случаях оформления веб страниц
Ниже пример одной из "облачных" иконок
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="350" height="350" viewBox="0 0 150 150" >
<path fill="dodgerblue" d="M104.1,52.9c0.1-0.8,0.1-1.5,0.1-2.3c0-16.9-13.7-30.6-30.6-30.6c-10.3,0-19.4,5.1-25,12.9
c-1.7-0.4-3.5-0.6-5.3-0.6c-11.8,0-21.6,8.4-23.8,19.6c-4.7,0.9-8.9,3.3-12.2,6.5C2.8,62.8,0,68.9,0,75.7C0,89.1,10.9,100,24.3,100
h71.5c13.4,0,24.3-10.9,24.3-24.3C120,65.3,113.4,56.4,104.1,52.9z M69.1,59.2c0,26.3,0,0,0,26.3c-10.7,0-2.9,0-15.5,0
c0-26.3,0,0,0-26.3c-8.9,0,0,0-8.9,0c16.6-16.6,0,0,16.6-16.6c16.6,16.6,0,0,16.6,16.6C69.1,59.2,78,59.2,69.1,59.2z"/>
</svg>
Возник вопрос
Существует ли простой способ менять конфигурацию контура облака без его перерисовки в векторном редакторе?
Ответы (3 шт):
Техника решения создания разнообразных контуров облаков основывается на ответе:
Необычные эффекты stroke-dasharray, где использовался прием с нулевой длиной черточки в атрибуте stroke-dasharray и stroke-linecap="round"
Цель получить примерно такое облако:
Далее решение по шагам
- Я закрасил stroke в чёрный цвет, чтобы были видны окружности по периметру эллипса.
<svg id="svg1" xmlns="http://www.w3.org/2000/svg" width="600" height="600" viewBox="0 0 600 600">
<rect width="100%" height="100%" fill="dodgerblue" />
<ellipse id="el" cx="300" cy="300" rx="200" ry="100" fill="#fff" stroke="black" stroke-width="70" stroke-dasharray="0,60" stroke-linecap="round">
</svg>
- Если
stroke="white", то белый фон эллипса сольется с белыми кругами и останется только внешний контур фигуры, который уже будет отдаленно напоминать облако:
<svg id="svg1" xmlns="http://www.w3.org/2000/svg" width="600" height="600" viewBox="0 0 600 600">
<rect width="100%" height="100%" fill="dodgerblue" />
<ellipse id="el" cx="300" cy="300" rx="200" ry="100" fill="#fff" stroke="white" stroke-width="70" stroke-dasharray="0,60" stroke-linecap="round">
</svg>
Чтобы избавиться от этого однообразия краев облака, добавим второй эллипс с параметрами атрибутов, отличающимися от параметров первого эллипса.
Для наглядности взаимодействия, окружности на двух разных эллипсах закрашены в разные цвета: зелёный и жёлтый
<svg id="svg1" xmlns="http://www.w3.org/2000/svg" width="600" height="600" viewBox="0 0 600 600">
<defs>
<linearGradient id="grad" x2="0%" y2="100%" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#1E589A" />
<stop offset="100%" stop-color="#7db9e8" />
</linearGradient>
</defs>
<rect width="100%" height="100%" fill="url(#grad)" />
<!-- Меньшие зелёные окружности размер задает stroke-width="70" -->
<ellipse id="el" cx="300" cy="300" rx="200" ry="100" fill="#fff" stroke="green" stroke-width="70" stroke-dasharray="0,60" stroke-linecap="round">
</ellipse>
<!-- Большие жёлтые окружности размер задает stroke-width="100" -->
<ellipse id="an_e2" cx="300" cy="300" rx="200" ry="100" fill="#fff" stroke="gold" opacity="0.8" stroke-width="100" stroke-dasharray="0,100" stroke-linecap="round">
</ellipse>
</svg>
- Теперь присваиваем всем
stroke="white"
Внутри контуров всё сливается, остается более усложненная форма внешнего контура:
<svg id="svg1" xmlns="http://www.w3.org/2000/svg" width="600" height="600" viewBox="0 0 600 600">
<defs>
<linearGradient id="grad" x2="0%" y2="100%" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#1E589A" />
<stop offset="100%" stop-color="#7db9e8" />
</linearGradient>
</defs>
<rect width="100%" height="100%" fill="url(#grad)" />
<!-- Меньшие белые окружности, размер задает stroke-width="70" -->
<ellipse id="el" cx="300" cy="300" rx="200" ry="100" fill="#fff" stroke="#fff" stroke-width="70" stroke-dasharray="0,60" stroke-linecap="round">
</ellipse>
<!-- Большие белые окружности, размер задает stroke-width="100" -->
<ellipse id="an_e2" cx="300" cy="300" rx="200" ry="100" fill="#fff" stroke="#fff" opacity="0.95" stroke-width="100" stroke-dasharray="0,100" stroke-linecap="round">
</ellipse>
</svg>
- Добавляем анимацию радиусов эллипсов, чтобы облака немного изменялись:
<animate id="an_e1" attributeName="rx" begin="svg1.click" dur="30s"
values="200;250;200;200" fill="freeze" repeatCount="indefinite"/>
<svg id="svg1" xmlns="http://www.w3.org/2000/svg" width="400" height="400" viewBox="0 0 600 600">
<defs>
<linearGradient id="grad" x2="0%" y2="100%" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#1E589A" />
<stop offset="100%" stop-color="#7db9e8" />
</linearGradient>
<filter id="Shadow" x="-30%" y="-30%" width="200%" height="200%">
<feGaussianBlur in="SourceAlpha" stdDeviation="5" />
<feOffset dx="5" dy="5" />
<feComponentTransfer>
<feFuncA type="linear" slope="0.4"/>
</feComponentTransfer>
<feMerge>
<feMergeNode />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
<rect width="100%" height="100%" fill="url(#grad)" />
<g filter="url(#Shadow)">
<ellipse id="el" cx="300" cy="300" rx="200" ry="100" fill="#fff" stroke="white" stroke-width="70" stroke-dasharray="0,60" stroke-linecap="round" >
<animate id="an_e1" attributeName="rx" begin="svg1.click" dur="30s" values="200;250;200;200" fill="freeze" repeatCount="indefinite"/>
</ellipse>
<ellipse id="an_e2" cx="300" cy="300" rx="200" ry="100" fill="#fff" stroke="white" stroke-width="100" stroke-dasharray="0,100" stroke-linecap="round">
<animate attributeName="rx" begin="svg1.click" dur="20s" values="200;150;200;200" fill="freeze" repeatCount="indefinite" />
</ellipse>
</g>
<text x="12" y="145" font-size="32px" fill="silver" >Click me </text>
</svg>
Анимация облаков на растровой картинке
- Добавляем растровую картинку внутрь svg
<image opacity="1" href="https://i.stack.imgur.com/K8Ewi.jpg" width="100%" height="100%" />
Весь код, который реализует облака, переносим в секцию
<defs>Клонируем облака, попутно увеличивая, уменьшая клоны и попутно позиционируем их. Всё это делается внутри тега
<use>для каждой копии облакаДобавляем команды анимации для перемещения облаков и уменьшения их прозрачности в конце пути.
Анимация перемещения облаков начнется после клика
<svg id="svg1" xmlns="http://www.w3.org/2000/svg" width="1280" height="720" viewBox="0 0 2560 1440">
<defs>
<linearGradient id="grad" x2="0%" y2="100%" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#1E589A" />
<stop offset="100%" stop-color="#7db9e8" />
</linearGradient>
<filter id="Shadow" x="-30%" y="-30%" width="200%" height="200%">
<feGaussianBlur in="SourceAlpha" stdDeviation="5" />
<feOffset dx="5" dy="5" />
<feComponentTransfer>
<feFuncA type="linear" slope="0.4"/>
</feComponentTransfer>
<feMerge>
<feMergeNode />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<g id="cloud" filter="url(#Shadow)">
<ellipse id="el" cx="300" cy="300" rx="200" ry="100" fill="#fff" stroke="white" stroke-width="70" stroke-dasharray="0,60" stroke-linecap="round" >
</ellipse>
<ellipse id="an_e2" cx="300" cy="300" rx="200" ry="100" fill="#fff" stroke="white" stroke-width="100" stroke-dasharray="0,100" stroke-linecap="round">
</ellipse>
</g>
</defs>
<image opacity="1" href="https://i.stack.imgur.com/K8Ewi.jpg" width="100%" height="100%" />
<g id="G1" opacity="1">
<use x="650" y="-250" href="#cloud" transform="scale(0.2 0.2)" />
<use href="#cloud" transform="scale(0.25 0.2)" />
<use x="650" y="250" href="#cloud" transform="scale(0.35 0.25)" />
<use x="200" y="300" href="#cloud" transform="scale(0.55 0.5)" />
<!-- Анимация перемещения облаков -->
<animateTransform
id="move"
href="#G1"
attributeName="transform"
type="translate"
begin="svg1.click;op.end"
dur="20s"
values="
0, 0;
200,200;
400,200;
600,100;
800, 0"
repeatCount="1"
additive="sum" />
<!-- Анимация прозрачности облаков -->
<animate id="op"
attributeName="opacity"
begin="move.begin"
dur="20s"
to="0"
additive="sum"
/>
</g>
</svg>
Запуск и остановка анимации с того же места, где она была прервана
Чтобы добиться повторного пуска анимации после её остановки можно использовать методы JS: pauseAnimations() и unpauseAnimations()
Добавлены две кнопки: остановки и запуска с событием onclick.
let flag = 0,
svg = document.querySelector('svg');
let start = function(){
if(flag === 1){
Array.from(svg.querySelectorAll('animateTransform')).forEach(e => e.removeAttribute('begin'));
start = _ => svg.unpauseAnimations();
start();
}
flag++;
}
const pause = function(){
svg.pauseAnimations();
}
<svg id="svg1" xmlns="http://www.w3.org/2000/svg" width="1280" height="720" viewBox="0 0 2560 1440">
<defs>
<linearGradient id="grad" x2="0%" y2="100%" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#1E589A" />
<stop offset="100%" stop-color="#7db9e8" />
</linearGradient>
<filter id="Shadow" x="-30%" y="-30%" width="200%" height="200%">
<feGaussianBlur in="SourceAlpha" stdDeviation="5" />
<feOffset dx="5" dy="5" />
<feComponentTransfer>
<feFuncA type="linear" slope="0.4"/>
</feComponentTransfer>
<feMerge>
<feMergeNode />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<g id="cloud" filter="url(#Shadow)">
<ellipse id="el" cx="300" cy="300" rx="200" ry="100" fill="#fff" stroke="white" stroke-width="70" stroke-dasharray="0,60" stroke-linecap="round" >
</ellipse>
<ellipse id="an_e2" cx="300" cy="300" rx="200" ry="100" fill="#fff" stroke="white" stroke-width="100" stroke-dasharray="0,100" stroke-linecap="round">
</ellipse>
</g>
</defs>
<image opacity="1" href="https://i.stack.imgur.com/K8Ewi.jpg" width="100%" height="100%" />
<g id="G1" opacity="1">
<use x="650" y="-250" href="#cloud" transform="scale(0.2 0.2)" />
<use href="#cloud" transform="scale(0.25 0.2)" />
<use x="650" y="250" href="#cloud" transform="scale(0.35 0.25)" />
<use x="200" y="300" href="#cloud" transform="scale(0.55 0.5)" />
<!-- Анимация перемещения облаков -->
<animateTransform
id="move"
href="#G1"
attributeName="transform"
type="translate"
begin="gO1.click;move.end+1s"
dur="30s"
values="
0, 0;
200,200;
400,200;
600,100;
700, 0"
repeatCount="indefinite"
/>
<!-- Анимация прозрачности облаков -->
<animate id="op"
attributeName="opacity"
begin="gO1.click"
dur="30s"
to="0"
additive="sum"
/>
</g>
<!-- Блок управляющих кнопок -->
<g transform="scale(2.4) translate(400,-50)">
<g id="gO1" onclick='start();'>
<rect x="45" y="85" height="22" width="60" rx="5" fill="#0080B8" stroke="dodgerblue" />
<text x="62" y="102" font-size="16" fill="yellow">GO</text>
</g>
<g onclick='pause();'>
<rect x="110" y="85" height="22" width="60" rx="5" fill="crimson" stroke="red" />
<text x="120" y="102" font-size="16" fill="yellow">STOP</text>
</g>
</g>
</svg>
Ответ не совсем имеет отношение к вопросу, поэтому ни на что не претендует. А просто по мотивам, на скорую руку, самый простой пример с использованием технологии CSS и лёгкой анимации.
body {
height: 100vh;
overflow: hidden;
background: lightskyblue;
}
.cloud,
.cloud>div:nth-child(1) {
position: relative;
margin: 0 auto;
margin-top: 50px;
width: 300px;
height: 100px;
border-radius: 100px;
background: white;
animation: 6s infinite linear ani0;
}
.cloud>div:nth-child(1):before,
.cloud>div:nth-child(1):after,
.cloud>div:nth-child(2):before,
.cloud>div:nth-child(2):after {
content: '';
position: absolute;
top: 0;
width: 200px;
height: 100px;
border-radius: 200px;
background: white;
}
.cloud>div:nth-child(1):before {
right: 20px;
transform: rotate(-85deg);
animation: 3s infinite linear ani1;
}
.cloud>div:nth-child(1):after {
left: 20px;
transform: rotate(85deg);
animation: 3s infinite linear ani2;
}
.cloud>div:nth-child(2):before {
right: 20px;
width: 250px;
transform: rotate(-30deg);
animation: 3s infinite linear ani3;
}
.cloud>div:nth-child(2):after {
left: 20px;
width: 250px;
transform: rotate(30deg);
animation: 3s infinite linear ani4;
}
@keyframes ani0 {
0% {
transform: rotate(0deg);
}
25% {
transform: rotate(2deg);
}
500% {
transform: rotate(0deg);
}
75% {
transform: rotate(-2deg);
}
100% {
transform: rotate(0deg);
}
}
@keyframes ani1 {
0% {
transform: rotate(-85deg);
}
50% {
transform: rotate(-75deg);
}
100% {
transform: rotate(-85deg);
}
}
@keyframes ani2 {
0% {
transform: rotate(85deg);
}
50% {
transform: rotate(75deg);
}
100% {
transform: rotate(85deg);
}
}
@keyframes ani3 {
0% {
transform: rotate(-30deg);
}
50% {
transform: rotate(-20deg);
}
100% {
transform: rotate(-30deg);
}
}
@keyframes ani4 {
0% {
transform: rotate(30deg);
}
50% {
transform: rotate(20deg);
}
100% {
transform: rotate(30deg);
}
}
.cloud>span {
display: block;
position: absolute;
top: 60%;
left: calc(50% - 50px);
width: 100px;
height: 40px;
border-bottom: 2px solid lightskyblue;
box-shadow: 0px -2px 2px 0px rgba(30, 60, 80, 0.2) inset;
border-radius: 100%;
}
.cloud>span:before,
.cloud>span:after {
content: "";
position: absolute;
top: -40px;
width: 20px;
height: 20px;
border-radius: 100%;
background: lightskyblue;
background: radial-gradient(cornflowerblue, cornflowerblue 2px, lightskyblue 6px, lightskyblue 100%);
}
.cloud>span:before {
left: -20px;
}
.cloud>span:after {
right: -20px;
}
<div class="cloud"><div></div><div></div><span></span></div>
К облачку добавим солнышко, чтобы на душе всем нам стало светлее и теплее =)
body {
height: 100vh;
overflow: hidden;
background: lightskyblue;
}
/*Облако*/
.cloud {
position: relative;
margin: 0 auto;
margin-top: 50px;
margin-right: 0;
width: 300px;
height: 100px;
}
.cloud>div:nth-child(1),
.cloud>div:nth-child(2) {
position: absolute;
top: 0;
left: 0;
width: 300px;
height: 100px;
border-radius: 100px;
background: white;
animation: 6s infinite linear ani0;
}
.cloud>div:nth-child(1):before,
.cloud>div:nth-child(1):after,
.cloud>div:nth-child(2):before,
.cloud>div:nth-child(2):after {
content: '';
position: absolute;
top: 0;
width: 200px;
height: 100px;
border-radius: 200px;
background: white;
}
.cloud>div:nth-child(1):before {
right: 20px;
transform: rotate(-85deg);
animation: 3s infinite linear ani1;
}
.cloud>div:nth-child(1):after {
left: 20px;
transform: rotate(85deg);
animation: 3s infinite linear ani2;
}
.cloud>div:nth-child(2):before {
right: 20px;
width: 250px;
transform: rotate(-30deg);
animation: 3s infinite linear ani3;
}
.cloud>div:nth-child(2):after {
left: 20px;
width: 250px;
transform: rotate(30deg);
animation: 3s infinite linear ani4;
}
@keyframes ani0 {
0% {
transform: rotate(0deg);
}
25% {
transform: rotate(2deg);
}
500% {
transform: rotate(0deg);
}
75% {
transform: rotate(-2deg);
}
100% {
transform: rotate(0deg);
}
}
@keyframes ani1 {
0% {
transform: rotate(-85deg);
}
50% {
transform: rotate(-75deg);
}
100% {
transform: rotate(-85deg);
}
}
@keyframes ani2 {
0% {
transform: rotate(85deg);
}
50% {
transform: rotate(75deg);
}
100% {
transform: rotate(85deg);
}
}
@keyframes ani3 {
0% {
transform: rotate(-30deg);
}
50% {
transform: rotate(-20deg);
}
100% {
transform: rotate(-30deg);
}
}
@keyframes ani4 {
0% {
transform: rotate(30deg);
}
50% {
transform: rotate(20deg);
}
100% {
transform: rotate(30deg);
}
}
.cloud>span {
display: block;
position: absolute;
top: 60%;
left: calc(50% - 50px);
width: 100px;
height: 40px;
border-bottom: 2px solid lightskyblue;
box-shadow: 0px -2px 2px 0px rgba(30, 60, 80, 0.2) inset;
border-radius: 100%;
}
.cloud>span:before,
.cloud>span:after {
content: "";
position: absolute;
top: -40px;
width: 20px;
height: 20px;
border-radius: 100%;
background: lightskyblue;
background: radial-gradient(cornflowerblue, cornflowerblue 2px, lightskyblue 6px, lightskyblue 100%);
animation: 6s infinite linear ani5;
}
.cloud>span:before {
left: -20px;
}
.cloud>span:after {
right: -20px;
}
@keyframes ani5 {
2% {
transform: scaleY(1);
}
4% {
transform: scaleY(0.1);
}
6% {
transform: scaleY(1);
}
8% {
transform: scaleY(0.1);
}
10% {
transform: scaleY(1);
}
100% {
transform: scaleY(1);
}
}
/*Солнце*/
.sun {
position: absolute;
top: 50px;
left: 50px;
width: 100px;
height: 100px;
background-color: yellow;
border-radius: 100px/100px 100px 100px 100px;
box-shadow: 0px 0px 20px yellow;
animation: sun 30s linear infinite;
}
@keyframes sun {
0% {
border-radius: 100px/100px 100px 100px 100px;
}
2% {
border-radius: 50px/50px 50px 100px 100px;
}
4% {
border-radius: 100px/50px 50px 100px 100px;
}
6% {
border-radius: 100px/100px 50px 100px 100px;
}
8% {
border-radius: 100px/100px 100px 100px 100px;
}
10% {
border-radius: 50px/50px 50px 100px 100px;
}
12% {
border-radius: 100px/100px 100px 100px 100px;
}
18% {
transform: rotate(0deg);
}
50% {
transform: rotate(180deg);
}
100% {
transform: rotate(0deg);
}
}
.sun>div {
position: absolute;
top: -50%;
left: 50%;
height: 30px;
width: 8px;
background: yellow;
box-shadow: 0px 0px 20px yellow;
border-radius: 10px;
transform: translateX(-50%);
}
.sun>div:nth-child(2) {
top: -41%;
left: -2%;
transform: rotate(-30deg);
}
.sun>div:nth-child(3) {
top: -10%;
left: -30%;
transform: rotate(-60deg);
}
.sun>div:nth-child(4) {
top: 30%;
left: -40%;
transform: rotate(-90deg);
}
.sun>div:nth-child(5) {
top: 70%;
left: -30%;
transform: rotate(-120deg);
}
.sun>div:nth-child(6) {
top: auto;
bottom: -41%;
left: -2%;
transform: rotate(30deg);
}
.sun>div:nth-child(7) {
top: auto;
bottom: -50%;
left: 50%;
transform: translateX(-50%);
}
.sun>div:nth-child(8) {
top: auto;
bottom: -41%;
left: auto;
right: -2%;
transform: rotate(-30deg);
}
.sun>div:nth-child(9) {
top: auto;
bottom: -10%;
left: auto;
right: -30%;
transform: rotate(-60deg);
}
.sun>div:nth-child(10) {
top: auto;
bottom: 30%;
left: auto;
right: -40%;
transform: rotate(-90deg);
}
.sun>div:nth-child(11) {
top: -41%;
left: auto;
right: -2%;
transform: rotate(30deg);
}
.sun>div:nth-child(12) {
top: -10%;
right: -30%;
left: auto;
transform: rotate(60deg);
}
.sun>span {
display: block;
position: absolute;
top: 70%;
left: calc(50% - 5px);
width: 20px;
height: 6px;
border-bottom: 2px solid yellowgreen;
box-shadow: 0px -2px 4px 0px rgb(30 60 80 / 20%) inset;
border-radius: 100%;
background: linear-gradient( 45deg, transparent, transparent 6px, red);
}
.sun>span:before,
.sun>span:after {
content: "";
position: absolute;
top: -40px;
width: 14px;
height: 12px;
border-radius: 100%;
background: lightskyblue;
background: radial-gradient(green, green 1px, yellowgreen 3px, yellowgreen 100%);
animation: 30s infinite linear sun1;
}
@keyframes sun1 {
13.5% {
transform: scaleY(1);
}
13.8% {
transform: scaleY(0.1);
}
14.1% {
transform: scaleY(1);
}
14.4% {
transform: scaleY(0.1);
}
14.7% {
transform: scaleY(1);
}
100% {
transform: scaleY(1);
}
}
.sun>span:before {
left: -12px;
}
.sun>span:after {
right: -12px;
}
<div class="cloud"><div></div><div></div><span></span></div>
<div class="sun"><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><span></span></div>


