6

Облака очень часто используются и в виде иконок и как отдельный элемент фона, фоновой анимации и во многих других случаях оформления веб страниц

Ниже пример одной из "облачных" иконок

<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>

Возник вопрос
Существует ли простой способ менять конфигурацию контура облака без его перерисовки в векторном редакторе?

Alexandr_TT
  • 110,146
  • 23
  • 114
  • 384

3 Answers3

7

Техника решения создания разнообразных контуров облаков основывается на ответе:
Необычные эффекты 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">
&lt;/ellipse&gt;
         &lt;!-- Большие жёлтые окружности размер задает stroke-width="100" --&gt;
  &lt;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"&gt;

  &lt;/ellipse&gt;        

</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">
&lt;/ellipse&gt;
         &lt;!-- Большие белые окружности, размер задает stroke-width="100" --&gt;
  &lt;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"&gt;
 &lt;/ellipse&gt;        

</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>
  &lt;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"&gt;
        &lt;animate attributeName="rx" begin="svg1.click" dur="20s" values="200;150;200;200" fill="freeze" repeatCount="indefinite" /&gt; 
  &lt;/ellipse&gt; 

</g>
<text x="12" y="145" font-size="32px" fill="silver" >Click me </text>

</svg>

Alexandr_TT
  • 110,146
  • 23
  • 114
  • 384
2

Ответ не совсем имеет отношение к вопросу, поэтому ни на что не претендует. А просто по мотивам, на скорую руку, самый простой пример с использованием технологии 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>
Sevastopol'
  • 28,195
1

Анимация облаков на растровой картинке

  • Добавляем растровую картинку внутрь 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>

  &lt;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"&gt;
  &lt;/ellipse&gt; 

</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" />

 &lt;!-- Анимация прозрачности облаков --&gt;
 &lt;animate id="op"
   attributeName="opacity"
   begin="move.begin"
   dur="20s"
   to="0"
   additive="sum"
     /&gt;

</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>

  &lt;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"&gt;
  &lt;/ellipse&gt; 

</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" />

 &lt;!-- Анимация прозрачности облаков --&gt;
 &lt;animate id="op"
   attributeName="opacity"
   begin="gO1.click"
   dur="30s"
   to="0"
   additive="sum"
     /&gt; 

</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>

Alexandr_TT
  • 110,146
  • 23
  • 114
  • 384