17

На многих сайтах стали появляться эффекты вращения окружностей с симметрично вырезанными небольшими участками. Смотрится хорошо. Как повторить данный эффект?

У меня получилось вырезать один сегмент с помощью атрибутов stroke-dasharray

Ниже код:

.txt1  {fill:white; pointer-events:none;}
.rect {fill:gray;}
.txt1:hover {fill:white;}
.rect:hover {fill:black; transition:fill 0.5s all;}
<svg id="svg1" version="1.1" xmlns="http://www.w3.org/2000/svg" 
    xmlns:xlink="http://www.w3.org/1999/xlink"  width="320" height="60" viewBox="0 0 350 60" >   
   <g id="gr1" >

<rect class="rect" x="62" y="11" rx="10" width="250" height="40" /> <text class="txt1" x="75" y="40" font-size="22" > SPECIAL OPERATIONS </text> <circle id="crc1" cx="30" cy="30" r="20" stroke='grey' stroke-width="3" fill='transparent' stroke-dasharray="115.66 10" stroke-dashoffset="-35.41" > </circle>
</g> </svg>

Как вырезать второй симметричный сегмент и заставить их вращаться при наведении на надпись?

Update

Добавлен новый ответ:

вариант - только HTML и CSS @UModeL

Дополнительные условия для конкурса:

Было бы очень интересно получить ответы с решением CSS, JS реализующие анимации, как в ответе анимации с SVG

  • Вращение одного сегмента
  • Техника создания анимаций фигур с количеством сегментов больше двух
  • Анимация симметричного заполнения фигур из одной точки

Предпочтения в выборе победителя при равенстве решений, будут отданы ответу с хорошо комментированным кодом.

Ответы с применением плагинов, сторонних библиотек, как конкурсный ответ приниматься не будут.

UPDATE 24.02.2019 г.

Поздравляю победителя конкурса UModeL

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

Alexandr_TT
  • 110,146
  • 23
  • 114
  • 384

4 Answers4

21

Только HTML и CSS. Border-color и rotate псевдоэлемента

Сразу оговорюсь, что SVG предлагает намного большие возможности, по части форм и анимации (что видно из соседних ответов). Правда, есть пара минусов - это утяжеление кода разметки (при этом, стили и скрипты никто не отменял) и изучение SVG (хотя "порог входа" не высок, но с ходу понять не так просто).

Для простых фигур, достаточно минимальной HTML-разметки, всё остальное же достигается с помощью CSS:

.spin,
.spin:after {
  display: inline-block;
  width: 40px;
  height: 40px;
  vertical-align: middle;
  box-sizing: border-box;
  /* Толщина окружности */
  border: 3px solid grey;
  border-radius: 50%;
  border-color: grey transparent grey transparent;
}
.spin {
  position: relative;
  margin: 10px 8px;
  /* Начальный угол */
  transform: rotate(0deg);
  transition: 1.5s ease;
}
.spin:after {
  content: '';
  position: absolute;
  z-index: 1;
  top: -3px;
  left: -3px;
  /* Размер зазоров */
  transform: rotate(65deg);
}

#spin { width: 0; height: 0; border: none; position: absolute; } #spin:hover+.spin { transform: rotate(720deg); }

label { display: inline-block; height: 38px; vertical-align: middle; border-radius: 10px; font: 20px/40px "Times New Roman"; text-align: center; transition: .3s ease; background: grey; color: white; } label:hover { background: black; }

<input id="spin">
<div class="spin"></div><label for="spin">&nbsp;&nbsp;SPECIAL&nbsp;OPERATIONS&nbsp;&nbsp;</label>

Лоадер

Создание можно разделить на несколько этапов:

  1. Создаём в HTML-разметке блок <div class="spin"></div>;
  2. Создаём в CSS правило .spin, .spin:after {} и добавляем следующие свойства:
    • width и height - указываем одинаковые значения для ширины и высоты;
    • box-sizing: border-box; - чтобы толщина рамки не влияла на окружающие элементы;
    • border: 3px solid grey; - собственно, рамка с указанием толщины;
    • border-radius: 50%; - скругляем углы элемента, превращая т.о. в круг;
    • (Внимание! Магия:) Указываем разным сторонам рамки разный цвет. В данном случае, требуется задать, попарно для параллельных сторон, прозрачность и основной цвет - border-color: grey transparent grey transparent;.
  3. Создаём правило .spin {} со свойствами:
    • position: relative; - для правильного позиционирования псевдоэлемента;
    • transform: rotate(0deg); - угол поворота в исходном положении блока;
    • transition: 1.5s ease; - задаём переход, чтобы происходила плавная анимация вращения, а не резкий скачок от начального положения к конечному
  4. Создаём правило .spin:after {} и свойства:
    • position: absolute; z-index: 1; top: -3px; left: -3px; - размещаем и выравниваем псевдоэлемент относительно основного блока;
    • (Внимание! Магия:) С помощью transform: rotate(65deg); задаём угол поворота псевдоэлемента, тем самым меняя ширину зазоров.

Принцип станет понятнее, если запустить пример ниже и подвигать ползунки:

var oControl=document.querySelector('.control'),oCode=document.querySelector('.code>pre');oControl.addEventListener('input',function(ev){document.documentElement.style.setProperty(`--${ev.target.id}`,ev.target.value);if(ev.target.id=='spin_width'){oCode.innerText=oCode.innerText.replace(/border: \d+px/gi,`border: ${ev.target.value}px`)};if(ev.target.id=='spin_angle'){oCode.innerText=oCode.innerText.replace(/(угол[\s\S]+?rotate\()[-\d]+(deg\))/gi,`$1${ev.target.value}$2`)};if(ev.target.id=='spin-a_angle'){oCode.innerText=oCode.innerText.replace(/(зазоров[\s\S]+?rotate\()[-\d]+(deg\))/gi,`$1${ev.target.value}$2`)}});oControl.addEventListener('mouseover',function(ev){if(ev.target.id=='spin-a_angle'){document.documentElement.style.setProperty(`--spin-a_color`,'rgba(255, 0, 0, 0.5)')};if(ev.target.tagName=='INPUT'){oCode.className=ev.target.id}});oControl.addEventListener('mouseout',function(ev){if(ev.target.id=='spin-a_angle'){document.documentElement.style.setProperty(`--spin-a_color`,'rgba(128, 128, 128, 1)')}})
:root{--spin_color:rgba(128,128,128,1);--spin-a_color:rgba(128,128,128,1);--spin_width:3;--spin_angle:0;--spin-a_angle:65;--code-top:.2em}.wrapper_621x183{position:relative;display:block;width:621px;height:183px;min-width:621px;min-height:183px;max-width:621px;max-height:183px;margin:0 auto;border:0 dashed #ccc}.control{position:absolute;z-index:10;top:10px;right:10px;display:inline-flex;flex-flow:column nowrap;background:rgba(230,230,230,1);box-shadow:2px 10px 20px -7px rgba(0,0,0,.3);padding:5px;border-radius:5px;font:12px/20px 'Arial';text-align:center}.code{position:absolute;z-index:5;bottom:1px;left:10px;width:540px;height:98px;background:rgba(255,255,255,.9);box-shadow:inset 0 3px 23px -4px rgba(0,0,0,.3);padding:8px;border-radius:4px;overflow:hidden}.code>pre{position:absolute;top:var(--code-top);margin:0;font:13px 'Consolas','Courier New',monospace;transition:top 1.5s ease}.code>pre.spin_width{--code-top:.2em}.code>pre.spin_angle{--code-top:-8.5em}.code>pre.spin-a_angle{--code-top:-17.5em}.spin,.spin:after{display:inline-block;width:40px;height:40px;vertical-align:middle;box-sizing:border-box;border:calc(var(--spin_width) * 1px) solid var(--spin_color);border-radius:50%}.spin{position:relative;margin:10px 8px;border-color:var(--spin_color) transparent var(--spin_color) transparent;transform:rotate(calc(var(--spin_angle) * -1deg));transition:1.5s ease}.spin:after{content:'';position:absolute;z-index:1;top:calc(var(--spin_width) * -1px);left:calc(var(--spin_width) * -1px);border-color:var(--spin-a_color) transparent var(--spin-a_color) transparent;transform:rotate(calc(var(--spin-a_angle) * 1deg))}#spin{width:0;height:0;border:none;position:absolute}#spin:hover+.spin{transform:rotate(720deg)}label{display:inline-block;height:38px;vertical-align:middle;border-radius:10px;font:20px/40px 'Times New Roman';text-align:center;transition:.3s ease;background:grey;color:white}label:hover{background:black}
<div class="wrapper_621x183"> <input id="spin"> <div class="spin"></div><label for="spin">&nbsp;&nbsp;SPECIAL&nbsp;OPERATIONS&nbsp;&nbsp;</label> <div class="control"> <div>Толщина окружности<br><input id="spin_width" min="1" max="20" value="3" type="range"></div><div>Начальный угол<br><input id="spin_angle" min="0" max="360" value="0" type="range"></div><div>Размер зазоров<br><input id="spin-a_angle" min="0" max="90" value="65" type="range"></div></div><div class="code"> <pre>.spin,<br>.spin:after {<br>  ···<br>  /* Толщина окружности */<br>  border: 3px solid grey;<br>  ···<br>}<br><br>.spin {<br>  ···<br>  /* Начальный угол */<br>  transform: rotate(-360deg);<br>  ···<br>}<br><br><br>.spin:after {<br>  ···<br>  /* Размер зазоров */<br>  transform: rotate(65deg);<br>  ···<br>}</pre> </div></div>

Кнопка

Кнопка реализована через тег <label>, путём превращения его в блочный элемент с помощью свойства display: block; с дальнейшей стилизацией.

Использование <label> не случайно и вызвано желанием отказаться от скриптов в конкретном примере, при реализации столь простой задачи. А также, чтобы продемонстрировать один из способов применения данного тега.

Тег <label> (пер. метка), в данном примере, используется для "удалённого" управления состоянием элемента <input>. Т.е. сама метка может располагаться в любом месте страницы (при этом, совсем необязательно рядом с тегом <input> с которым она связана). Связь метки и <input> осуществляется с помощью атрибута for, в значении которого указывается id управляемого элемента:

<input id="spin"> <label for="spin">

Более того, меток указывающих на один и тот же элемент может быть несколько, размещаться они могут в произвольных местах документа и иметь разную стилизацию:

label {
  display: block;
  margin: 15px auto;
  box-sizing: border-box;
  text-align: center;
  box-shadow: 0 5px 7px -3px black;
  transition: .3s ease;
}
label:hover { box-shadow: 0 2px 4px -2px black; }

.first { width: 130px; height: 25px; border: 2px solid #f00; border-radius: 8px; } .second { width: 130px; height: 25px; border: 2px solid #0f0; border-radius: 0 8px 0 8px; background-image: linear-gradient(to bottom, lime, gold, lime); }

#spin { width: 0; height: 0; border: none; position: absolute; } #spin:hover+.spin { transform: rotate(360deg); }

.spin, .spin:after { display: block; width: 70px; height: 70px; box-sizing: border-box; border: 5px solid grey; border-radius: 50%; border-color: grey transparent grey transparent; } .spin { position: relative; margin: 10px auto; transform: rotate(-360deg); transition: 1.5s ease; } .spin:after { content: ''; position: absolute; top: -5px; left: -5px; transform: rotate(65deg); }

<label for="spin" class="first">Наведи на меня!</label>
<input id="spin"><div class="spin"></div>
<label for="spin" class="second">... Или на меня!</label>

Важно! Единственным условием для того, чтобы управляемый элемент своим состоянием мог воздействовать на соседний элемент, <input> должен располагаться в разметке непосредственно перед нужным элементом, а в CSS нужно использовать селектор выбора соседнего элемента - +:

input#spin:hover + div.spin { }

При этом сам <input> нужно скрыть, например, указав в стилях:

input#spin {
  width: 0;
  height: 0;
  border: none;
  position: absolute;
}

Почему не display: none;? Потому, что в некоторых браузерах перестают работать связи с метками с элементами скрытыми таким способом.


Варианты

Один сегмент

Изменив всего один параметр - border-color: grey grey grey transparent;, можно получить вращение только одного зазора:

.spin,
.spin:after {
  display: inline-block;
  width: 40px;
  height: 40px;
  vertical-align: middle;
  box-sizing: border-box;
  /* Толщина окружности */
  border: 3px solid grey;
  border-radius: 50%;
  border-color: grey grey grey transparent;
}
.spin {
  position: relative;
  margin: 10px 8px;
  /* Начальный угол */
  transform: rotate(0deg);
  transition: 1.5s ease;
}
.spin:after {
  content: '';
  position: absolute;
  z-index: 1;
  top: -3px;
  left: -3px;
  /* Размер зазоров */
  transform: rotate(65deg);
}

#spin { width: 0; height: 0; border: none; position: absolute; } #spin:hover+.spin { transform: rotate(720deg); }

label { display: inline-block; height: 38px; vertical-align: middle; border-radius: 10px; font: 20px/40px "Times New Roman"; text-align: center; transition: .3s ease; background: grey; color: white; } label:hover { background: black; }

<input id="spin">
<div class="spin"></div><label for="spin">&nbsp;&nbsp;SPECIAL&nbsp;OPERATIONS&nbsp;&nbsp;</label>

Три сегмента

Чтобы получить три зазора, понадобится добавить ещё один псевдоэлемент .spin:before { }, продублировав для него свойства из .spin:after { }. Затем, у основного блока и псевдоэлементов задать свойство transform: rotate( ); с разницей в 120deg. Также, нужно применить нашу "магию" следующим образом - border-color: grey transparent transparent transparent;:

.spin,
.spin:after,
.spin:before {
  display: inline-block;
  width: 40px;
  height: 40px;
  vertical-align: middle;
  box-sizing: border-box;
  /* Толщина окружности */
  border: 3px solid grey;
  border-radius: 50%;
  border-color: grey transparent transparent transparent;
}
.spin {
  position: relative;
  margin: 10px 8px;
  /* Начальный угол */
  transform: rotate(0deg);
  transition: 1.5s ease;
}
.spin:after {
  content: '';
  position: absolute;
  z-index: 1;
  top: -3px;
  left: -3px;
  /* Размер зазоров */
  transform: rotate(120deg);
}
.spin:before {
  content: '';
  position: absolute;
  z-index: 1;
  top: -3px;
  left: -3px;
  /* Размер зазоров */
  transform: rotate(240deg);
}

#spin { width: 0; height: 0; border: none; position: absolute; } #spin:hover+.spin { transform: rotate(720deg); }

label { display: inline-block; height: 38px; vertical-align: middle; border-radius: 10px; font: 20px/40px "Times New Roman"; text-align: center; transition: .3s ease; background: grey; color: white; } label:hover { background: black; }

<input id="spin">
<div class="spin"></div><label for="spin">&nbsp;&nbsp;SPECIAL&nbsp;OPERATIONS&nbsp;&nbsp;</label>

Варианты с другим количеством сегментов, резко увеличивают количество кода и усложняют разметку, поэтому реализация HTML+CSS будет нецелесообразной и лучше использовать другой способ.


Анимация появления рамки

Без сомнения, при творческом подходе, возможно создать почти любые эффекты, только средствами CSS. Но, нужно задуматься - "стоит ли овчинка выделки?" Возможно, реализация задумки, где с помощью другой технологии, можно обойтись парой-тройкой строк, не стоит "простыни" кода:

.anistroke {
  position: relative;
  width: 140px;
  height: 40px;
  border-bottom: 3px solid transparent;
  text-align: center;
  font: 18px/40px 'Arial';
  box-sizing: border-box;
  animation: aniblock 1.5s steps(1, end) forwards;
}

@keyframes aniblock { 50%, 100% { border-bottom: 3px solid grey; } }

.anistroke::after, .anistroke::before { content: ''; position: absolute; z-index: 1; bottom: -3px; width: 0px; height: 0px; border-top: 0px solid transparent; border-bottom: 3px solid grey; } .anistroke::after { left: 50%; border-left: 3px solid grey; animation: aniafter 1.5s linear forwards; } .anistroke::before { right: 50%; border-right: 3px solid grey; animation: anibefore 1.5s linear forwards; }

@keyframes aniafter { 50% { left: 0%; height: 0px; width: 70px; } 75% { height: 37px; width: 0px; border-top: 0px solid transparent; border-bottom: 3px solid grey; } 75.01% { border-top: 3px solid grey; border-bottom: 0px solid transparent; } 100% { left: 0%; height: 37px; width: 30px; border-top: 3px solid grey; border-bottom: 0px solid transparent; } }

@keyframes anibefore { 50% { right: 0%; height: 0px; width: 70px; } 75% { height: 37px; width: 0px; border-top: 0px solid transparent; border-bottom: 3px solid grey; } 75.01% { border-top: 3px solid grey; border-bottom: 0px solid transparent; } 100% { right: 0%; height: 37px; width: 30px; border-top: 3px solid grey; border-bottom: 0px solid transparent; } }

<div class="anistroke">anistroke</div>
UModeL
  • 34,026
  • 6
  • 29
  • 71
  • 1
    Хороший ответ. Просьба хоть минимальные комментарии добавить. В частности как вырез достигается. И вторая просьба, второй вариант, если есть желание, сделать с одной кнопкой – Alexandr_TT Feb 12 '19 at 13:03
  • ну наворотов добавь каких-нибудь. Мы же люди творческие, необязательно точь в точь, как в вопросе, я же вопрос выдумал, чтобы люди здесь проявили себя – Alexandr_TT Feb 12 '19 at 13:16
  • просто шикарно и очень полезно для изучения. Люди не проходите мимо :) Изучайте и берите на вооружение! – Alexandr_TT Feb 17 '19 at 11:18
17
  • Рассчитаем длину окружности при заданном радиусе 20px

C = 2 * 3.1415 * 20 = 125.66

Длина половины окружности равна 62,83 Если взять длину, вырезаемого сегмента равным 10px то формула stroke-dasharray будет такой: stroke-dasharray="52.83 10"

Первая цифра 52.83 в формуле, это длина черты, вторая 10 - пробел.

.txt1  {fill:white; pointer-events:none;}
.rect {fill:gray;}
.txt1:hover {fill:white;}
.rect:hover {fill:black; transition:fill 0.5s all;}
<svg id="svg1" version="1.1" xmlns="http://www.w3.org/2000/svg" 
    xmlns:xlink="http://www.w3.org/1999/xlink"  width="320" height="60" viewBox="0 0 350 60" >   
   <g id="gr1" >

<rect class="rect" x="62" y="11" rx="10" width="250" height="40" /> <text class="txt1" x="75" y="40" font-size="22" > SPECIAL OPERATIONS </text> <circle id="crc1" cx="30" cy="30" r="20" stroke='grey' stroke-width="3" fill='transparent' stroke-dasharray="52.83 10" stroke-dashoffset="-35.41" > </circle>
</g> </svg>

  • Добавляем команду анимации вращения окружности.

На самом деле мы не вращаем окружность, а сдвигаем начало сегментов с помощью анимации stroke-dashoffset

При наведении курсора будет работать событие begin="gr1.mouseover" окружность будет вращаться в одну сторону.

При уходе курсора с надписи begin="gr1.mouseout" окружность будет вращаться в противоположную сторону.

Анимация начинается при наведении курсора на надпись

.txt1  {fill:white; pointer-events:none;}
.rect {fill:gray;}
.txt1:hover {fill:white;}
.rect:hover {fill:black; transition:fill 0.5s all;}
<svg id="svg1" version="1.1" xmlns="http://www.w3.org/2000/svg" 
    xmlns:xlink="http://www.w3.org/1999/xlink"  width="320" height="60" viewBox="0 0 350 60" >   
   <g id="gr1" >
 &lt;rect class="rect"  x="62" y="11" rx="10" width="250" height="40" /&gt;
  &lt;text class="txt1" x="75" y="40" font-size="22" &gt; SPECIAL OPERATIONS &lt;/text&gt;
&lt;/g&gt;  
    &lt;circle id="crc1" cx="30" cy="30" r="20"   stroke='grey' stroke-width="3" fill='transparent' 
       stroke-dasharray="52.83 10"  stroke-dashoffset="-4" &gt;
      &lt;animate
           attributeName="stroke-dashoffset"
           values="-10;105.66;-10"
           dur="0.5s"
           begin="gr1.mouseover"
           repeatCount="1"
           restart="whenNotActive" /&gt; 
    &lt;animate
      attributeName="stroke-dashoffset"
      values="105.66;0;105.66"
      dur="0.5s"
      begin="gr1.mouseout"
      repeatCount="1"
      restart="whenNotActive" /&gt; 
   &lt;/circle&gt;    

</svg>

2-ой вариант

Сегменты расположены вертикально

.txt1  {fill:white; pointer-events:none;}
.rect {fill:gray;}
.txt1:hover {fill:white;}
.rect:hover {fill:black; transition:fill 0.5s all;}
<svg id="svg1" version="1.1" xmlns="http://www.w3.org/2000/svg" 
    xmlns:xlink="http://www.w3.org/1999/xlink"  width="320" height="60" viewBox="0 0 350 60" >   
   <g id="gr1" >

<rect class="rect" x="62" y="11" rx="10" width="250" height="40" /> <text class="txt1" x="75" y="40" font-size="22" > SPECIAL OPERATIONS </text> </g>

<circle id="crc1" cx="30" cy="30" r="20" stroke='crimson' stroke-width="3" fill='transparent' stroke-dasharray="52.83 10" stroke-dashoffset="-35.41" > <animate attributeName="stroke-dashoffset" values="-10;105.66;-10" dur="0.5s" begin="gr1.mouseover" repeatCount="1" restart="whenNotActive" /> <animate attributeName="stroke-dashoffset" values="105.66;0;105.66" dur="0.5s" begin="gr1.mouseout" repeatCount="1" restart="whenNotActive" /> --> </circle>

</svg>

Alexandr_TT
  • 110,146
  • 23
  • 114
  • 384
  • 3
    Спасибо за подробное пояснение как это делается! –  Feb 11 '19 at 20:20
  • Интересно уже глянуть :))) –  Feb 12 '19 at 22:24
8

Варианты анимаций

  • Вращение одного сегмента
  • Техника создания анимаций фигур с количеством сегментов больше двух
  • Анимация симметричного заполнения фигур из одной точки

Вращение одного сегмента

Также используем атрибут stroke-dasharray сначала для вырезания сегмента. При полной длине окружности равной 125,66 и размере вырезаемого сегмента 10px получаем:

stroke-dasharray="115.66 10"

Анимация реализуется изменением stroke-dashoffset от максимума до минимума.

Запуск анимации при наведении курсора

.txt1  {fill:white; transition: all  1s ease; pointer-events:none;}
.rect {fill:gray; transition: all  1s ease;}
.txt1:hover {fill:white; }
.rect:hover {fill:black; }
#crc1 
{
stroke:#d5d5d5;
stroke-width:3; 
fill:transparent;
}
<svg id="svg1" version="1.1" xmlns="http://www.w3.org/2000/svg" 
    xmlns:xlink="http://www.w3.org/1999/xlink"  width="320" height="60" viewBox="0 0 350 60" >   
   <g id="gr1" >

<rect class="rect" x="62" y="11" rx="10" width="250" height="40" /> <text class="txt1" x="75" y="40" font-size="22" > SPECIAL OPERATIONS </text> </g>

<circle id="crc1" cx="30" cy="30" r="20"
stroke-dasharray="115.66 10" stroke-dashoffset="-35.41" > <animate attributeName="stroke-dashoffset" values="105.66;-10" dur="0.35s" begin="gr1.mouseover" end="gr1.mouseout" repeatCount="indefinite" restart="whenNotActive" />

</circle>

</svg>

Второй вариант

Движение по видимой круговой траектории

К предыдущему коду добавляется вторая окружность, которая рисует траекторию движения сегмента.

.txt1  {fill:white; pointer-events:none;}
.rect {fill:gray;}
.txt1:hover {fill:white;}
.rect:hover {fill:black; transition:fill 0.5s all;}
<svg id="svg1" version="1.1" xmlns="http://www.w3.org/2000/svg" 
    xmlns:xlink="http://www.w3.org/1999/xlink"  width="320" height="60" viewBox="0 0 350 60" >   
   <g id="gr1" >

<rect class="rect" x="62" y="11" rx="10" width="250" height="40" /> <text class="txt1" x="75" y="40" font-size="22" > SPECIAL OPERATIONS </text> </g> <circle id="crc2" cx="30" cy="30" r="20" stroke='black' stroke-width="3" fill='transparent'/> <circle id="crc1" cx="30" cy="30" r="20" stroke='#d5d5d5' stroke-width="3" fill='transparent' stroke-dasharray="115.66 10" stroke-dashoffset="-35.41" > <animate attributeName="stroke-dashoffset" values="105.66;-10" dur="0.35s" begin="gr1.mouseover" end="gr1.mouseout" repeatCount="indefinite" restart="whenNotActive" />

</circle>

</svg>

Техника создания анимаций фигур с количеством сегментов больше двух

Для более углубленного изучения есть топик на нашем сайте, прочитав который, вы в совершенстве будете владеть данной техникой.

Три сегмента

Допустим нам необходимо создать три вращающихся сегмента.

делим полную длину окружности - 125.66 / 3 = 41.88

В одном сегменте 41,88px должны уместится черта 33.88 + пробел 8px

Итого получилось - stroke-dasharray="33.88 8"

.txt1  {fill:white; pointer-events:none;}
.rect {fill:gray;}
.txt1:hover {fill:white;}
.rect:hover {fill:black; transition:fill 0.5s all;}
#crc1 {fill:transparent; stroke:#777777;}
<svg id="svg1" version="1.1" xmlns="http://www.w3.org/2000/svg" 
    xmlns:xlink="http://www.w3.org/1999/xlink"  width="320" height="60" viewBox="0 0 350 60" >   
   <g id="gr1" >

<rect class="rect" x="62" y="11" rx="10" width="250" height="40" /> <text class="txt1" x="75" y="40" font-size="22" > SPECIAL OPERATIONS </text> </g>

<circle id="crc1" cx="30" cy="30" r="20" stroke-width="3" stroke-dasharray="33.88 8" stroke-dashoffset="-4" > <animate attributeName="stroke-dashoffset" values="33.88;0" dur="0.2s" begin="gr1.mouseover" end="gr1.mouseout" repeatCount="indefinite" restart="whenNotActive" />

</circle>

</svg>

Четыре сегмента

125,66 / 4 = 31,415 stroke-dasharray="23.415 8"

.txt1  {fill:white; pointer-events:none;}
.rect {fill:gray;}
.txt1:hover {fill:white;}
.rect:hover {fill:black; transition:fill 0.5s all;}
<svg id="svg1" version="1.1" xmlns="http://www.w3.org/2000/svg" 
    xmlns:xlink="http://www.w3.org/1999/xlink"  width="320" height="60" viewBox="0 0 350 60" >   
   <g id="gr1" >

<rect class="rect" x="62" y="11" rx="10" width="250" height="40" /> <text class="txt1" x="75" y="40" font-size="22" > SPECIAL OPERATIONS </text> </g>

<circle id="crc1" cx="30" cy="30" r="20" stroke='#777777' stroke-width="3" fill='transparent' stroke-dasharray="23.415 8" stroke-dashoffset="-4" > <animate attributeName="stroke-dashoffset" values="54.83;0" dur="0.2s" begin="gr1.mouseover" end="gr1.mouseout" repeatCount="indefinite" restart="whenNotActive" /> </circle>

</svg>

Пять сегментов

125,66 / 5 = 25.13 stroke-dasharray="15.13 10"

.txt1  {fill:white; pointer-events:none;}
.rect {fill:gray;}
.txt1:hover {fill:white;}
.rect:hover {fill:black; transition:fill 0.5s all;}
<svg id="svg1" version="1.1" xmlns="http://www.w3.org/2000/svg" 
    xmlns:xlink="http://www.w3.org/1999/xlink"  width="320" height="60" viewBox="0 0 350 60" >   
   <g id="gr1" >

<rect class="rect" x="62" y="11" rx="10" width="250" height="40" /> <text class="txt1" x="75" y="40" font-size="22" > SPECIAL OPERATIONS </text> </g>

<circle id="crc1" cx="30" cy="30" r="20" stroke='#777777' stroke-width="3" fill='transparent' stroke-dasharray="15.13 10" stroke-dashoffset="-4" > <animate attributeName="stroke-dashoffset" values="54.83;0" dur="0.2s" begin="gr1.mouseover" end="gr1.mouseout" repeatCount="indefinite" restart="whenNotActive" /> </circle>

</svg>

Анимация симметричного заполнения фигур из одной точки

В этой технике используется четыре параметра атрибута stroke-dasharray

Проще понять на отрезке прямой, у которой в отличии от окружности не смыкаются конечные точки.

Допустим имеем такую запись stroke-dasharray="10 20"
Она означает, что чередуются на всей длине линии черта 10px и пробел 20px

<svg id="svg1" version="1.1" xmlns="http://www.w3.org/2000/svg" 
    xmlns:xlink="http://www.w3.org/1999/xlink"  width="320" height="60" viewBox="0 0 350 60" >

<polyline points="0,30 350,30" stroke-dasharray="10 20" stroke='#777777' stroke-width="3" fill='transparent' stroke-dashoffset="0" /> </svg>

Теперь добавляем ещё два параметра

stroke-dasharray="10 20 40 40"

<svg id="svg1" version="1.1" xmlns="http://www.w3.org/2000/svg" 
    xmlns:xlink="http://www.w3.org/1999/xlink"  width="320" height="60" viewBox="0 0 340 60" >

<polyline points="0,30 350,30" stroke-dasharray="10 20 40 40" stroke='#777777' stroke-width="3" fill='transparent' stroke-dashoffset="0" /> </svg>

В этом варианте 10 - черта, 20 - пробел, 40 черта, 40 пробел и снова 10 - черта 20 - пробел и так до конца линии.

Анимация линии из средней точки

<svg id="svg1" version="1.1" xmlns="http://www.w3.org/2000/svg" 
    xmlns:xlink="http://www.w3.org/1999/xlink"  width="320" height="60" viewBox="0 0 350 60" >

<path id="path" d="M 0 30 L 340 30" stroke='#777777' stroke-width="3" fill='transparent' stroke-dashoffset="0" > <animate attributeName="stroke-dasharray" from="0 170 0 170" to="0 0 340 0" dur="4s" begin="0s" repeatCount="1" restart="whenNotActive" fill="freeze" />
</path> </svg>

Линия длиной 340px, половина линии, средняя точка 170px

from="0 170 0 170" начало анимации - черта - 0, пробел длиною 170 px, черта - 0, пробел длиною 170 px то есть вся линия спрятана.

to="0 0 340 0" - черта длиною 0 пробел длиною 0, черта 340px, пробел - ноль. Так как на трех позициях нули, а одна черта имеет максимальную длину равную полной длине линии, то линия будет показана полностью.

На этом принципе действуют остальные примеры рисования из средней точки

Анимация рисования бордюра из средней точки

<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" 
   width="320" height="60" viewBox="0 0 350 60">
         <path id="shape" fill="none" stroke-width="3" stroke="#656E76"
              d="M 38.8 3.35 H 3.15 V 43.65 H 155.75 V 3.35 H 120.2" />
 <animate xlink:href="#shape" attributeName="stroke-dasharray" from="0 152.2 0 152.2" to="0 0 304.4 0" begin="0s" dur="1.4s" />
 </g>
</svg>   

Анимация вертикального эллипса

.txt1  {fill:white; pointer-events:none;}
.rect {fill:gray; transition: 0.8s  all;}
.txt1:hover {fill:white;}
.rect:hover {fill:crimson; transition: 0.8s all;}
<svg id="svg1" version="1.1" xmlns="http://www.w3.org/2000/svg" 
    xmlns:xlink="http://www.w3.org/1999/xlink"  width="320" height="60" viewBox="0 0 350 60" >   
   <g id="gr1" >
     <rect class="rect"  x="62" y="11" rx="10" width="250" height="40" />
       <text class="txt1" x="75" y="40" font-size="22" > SPECIAL OPERATIONS </text>
   </g>
     <g transform="translate(20 9)">
  <path d="m31 22a9 17 0 0 1-9 17 9 17 0 0 1-9-17 9 17 0 0 1 9-17 9 17 0 0 1 9 17z" 
    stroke='#d0d0d0' stroke-width="3" fill='transparent' />

<path d="m31 22a9 17 0 0 1-9 17 9 17 0 0 1-9-17 9 17 0 0 1 9-17 9 17 0 0 1 9 17z" stroke="crimson" stroke-width="3" fill="none" stroke-dasharray="0 42 0 42" stroke-dashoffset="-21" > <animate attributeName="stroke-dasharray" from="0 42 0 42" to="0 0 84 0" dur="0.4s" begin="gr1.mouseover" repeatCount="1" restart="whenNotActive" fill="freeze" /> <animate attributeName="stroke-dasharray" from="0 0 84 0" to="0 42 0 42" dur="0.4s" begin="gr1.mouseout" repeatCount="1" restart="whenNotActive" fill="freeze" /> </path> </g>
</svg>

Горизонтальный эллипс

.txt1  {fill:white; pointer-events:none;}
.rect {fill:gray;}
.txt1:hover {fill:white;}
.rect:hover {fill:crimson; transition: 0.5s all;}
<svg id="svg1" version="1.1" xmlns="http://www.w3.org/2000/svg" 
    xmlns:xlink="http://www.w3.org/1999/xlink"  width="320" height="60" viewBox="0 0 350 60" >   
   <g id="gr1" >
     <rect class="rect"  x="62" y="11" rx="10" width="250" height="40" />
       <text class="txt1" x="75" y="40" font-size="22" > SPECIAL OPERATIONS </text>
   </g>
     <g transform="translate(10 9)">
  <path d="m41 22a19 13 0 0 1-19 13 19 13 0 0 1-19-13 19 13 0 0 1 19-13 19 13 0 0 1 19 13z" 
    stroke='#d0d0d0' stroke-width="3" fill='transparent' />

<path d="m41 22a19 13 0 0 1-19 13 19 13 0 0 1-19-13 19 13 0 0 1 19-13 19 13 0 0 1 19 13z" stroke="crimson" stroke-width="3" fill="none" stroke-dasharray="0 50.5 0 50.5" stroke-dashoffset="-25" > <animate attributeName="stroke-dasharray" from="0 50.5 0 50.5" to="0 0 101 0" stroke-dashoffset="25" dur="0.8s" begin="gr1.mouseover" repeatCount="1" restart="whenNotActive" fill="freeze" />

&lt;animate

attributeName="stroke-dasharray" from="0 0 101 0" to="0 50.5 0 50.5" stroke-dashoffset="25" dur="0.8s" begin="gr1.mouseout" repeatCount="1" restart="whenNotActive" fill="freeze" /> </path> </g>
</svg>

Alexandr_TT
  • 110,146
  • 23
  • 114
  • 384
  • Как всегда на высоте! Беру на вооружение! –  Feb 12 '19 at 22:40
  • @leonid Наткнулся на топик с детальной разборкой stroke-dasharray. Вспомнил, что ты, как-то хотел поразбираться с ней – Alexandr_TT Nov 07 '21 at 14:17
7

От меня, как и в прошлый раз WebGL изврат в 100 строчек кода вместе с разметкой и с небольшим бонусом в виде displacement'a

введите сюда описание изображения

Все это математика во фрагментном шейдере и те же самые signed distance fields, о которых я уже писал в посте по ссылке в начале

Если коротко то вся соль, вот тут:

vec3 paintCircle (vec2 uv, vec2 center, float rad, float width) {

    // координаты пикселя относительно центра "круга"
    vec2 diff = center - uv;

    // расстояние до этой точки
    float len = length(diff);

    // расстояние до "круга"
    float circle = smoothstep(rad - width, rad, len) - 
                   smoothstep(rad, rad + width, len);

    // поворот текстурных координат относительно центра "круга"
    vec2 at = (uv-center) * rotate2d(rotation);

    // вырезание 2 частей окружности
    if (at.x - 0.05 < 0. && at.x + 0.05 > 0.)
        circle -= 1.0; 

    return vec3(circle);
}

<!DOCTYPE html>
<html lang="en">
<body>
    <script>
        let started = new Date().getTime();
        let canvas = document.createElement('canvas');
        document.body.append(canvas);
        let size = canvas.width = canvas.height = 150;
        let gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
    let pid = gl.createProgram();

    shader(`
        attribute vec2 coords;
        void main(void) {
            gl_Position = vec4(coords.xy, 0.0, 1.0);
        }
    `, gl.VERTEX_SHADER);

    shader(`
        precision highp float;

        uniform float time;
        uniform float rotation;
        uniform float displace;

        mat2 rotate2d(float angle) {
            return mat2(cos(angle),-sin(angle),
                        sin(angle), cos(angle));
        }

        float displacement(vec2 v1, vec2 v2, float strength, float speed) {
            return sin(
                dot(normalize(v1), normalize(v2)) * strength + time * speed
            ) / displace;
        }

        vec3 paintCircle (vec2 uv, vec2 center, float rad, float width) {
            vec2 diff = center-uv;
            float len = length(diff);
            len += displacement(diff, vec2(0.0, 1.0), 5.0, 2.0);
            len -= displacement(diff, vec2(1.0, 0.0), 5.0, 2.0);
            float circle = smoothstep(rad-width, rad, len) - smoothstep(rad, rad+width, len);
            vec2 at = (uv-center) * rotate2d(rotation);
            if (at.x - 0.05 &lt; 0. &amp;&amp; at.x + 0.05 &gt; 0.)
                circle -= 1.0;
            return vec3(circle);
        }

        void main(void) {
            vec2 uv = gl_FragCoord.xy / ${size}.;
            vec3 color = paintCircle(uv, vec2(0.5), 0.3, 0.1);
            color *= vec3(uv.x, uv.y, 0.7-uv.y*uv.x);
            color += paintCircle(uv, vec2(0.5), 0.3, 0.03);
            gl_FragColor = vec4(1.0-color, color.r+color.g+color.b);
        }
    `, gl.FRAGMENT_SHADER);

    gl.linkProgram(pid);
    gl.useProgram(pid);

    let array = new Float32Array([-1,  3, -1, -1, 3, -1]);
    gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
    gl.bufferData(gl.ARRAY_BUFFER, array, gl.STATIC_DRAW);

    let al = gl.getAttribLocation(pid, "coords");
    gl.vertexAttribPointer(al, 2 /*components per vertex */, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(al);

    let time = gl.getUniformLocation(pid, 'time');
    let rot = gl.getUniformLocation(pid, 'rotation');
    let displacement = gl.getUniformLocation(pid, 'displace');
    gl.uniform1f(displacement, 500);

    draw();
    var rotate = 0;

    function draw() {
        requestAnimationFrame(draw);
        let dt = new Date().getTime() - started;
        gl.uniform1f(rot, rotate * dt / 300);
        gl.uniform1f(time, dt / 1000);
        gl.viewport(0, 0, size, size);
        gl.clearColor(0, 0, 0, 0);
        gl.drawArrays(gl.TRIANGLES, 0, 3);
    }

    function shader(src, type) {
        let sid = gl.createShader(type);
        gl.shaderSource(sid, src);
        gl.compileShader(sid);
        gl.attachShader(pid, sid);
    }

    canvas.onmouseenter = function () {rotate = 1;};
    canvas.onmouseleave = function () {rotate = 0;};
&lt;/script&gt;
&lt;input type="range" min="10" max="1000" value="500" onchange="gl.uniform1f(displacement, this.value)"&gt;

</body> </html>

  • @Alexandr_TT все возможно, однако нужно ли, пост все равно не участвует в конкурсе, это я так решил развлечь себя, в перерыве от работы. – Stranger in the Q Feb 17 '19 at 16:56
  • странное ощущение, что крутится белая полоска над цельным кругом, а не две раздельные половинки – Grundy Jan 17 '22 at 16:00
  • @Grundy видимо так оно и устроено) – Stranger in the Q Jan 17 '22 at 16:01