Как вырезать два небольших сегмента из окружности и вращать их при наведении курсора

150
02 октября 2019, 12:00

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

У меня получилось вырезать один сегмент с помощью атрибутов 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

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

Answer 1
Только 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>

Answer 2
  • Рассчитаем длину окружности при заданном радиусе 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" > 
     
     <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='grey' stroke-width="3" fill='transparent'  
           stroke-dasharray="52.83 10"  stroke-dashoffset="-4" > 
	      <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>

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='grey' 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>	 

Answer 3

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

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

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

Также используем атрибут 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"  />  
		  
	   <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>

Answer 4

От меня, как и в прошлый раз 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 < 0. && at.x + 0.05 > 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;}; 
    </script> 
    <input type="range" min="10" max="1000" value="500" onchange="gl.uniform1f(displacement, this.value)"> 
</body> 
</html>

READ ALSO
HTML5 Canvas анимация

HTML5 Canvas анимация

Возникла такая трудность вывожу на экран(в canvas html5) массив объектов, и мне как то надо задать этим объектам разную скорость вращенияКак это...

125
Vue, vuetify и список для справочников

Vue, vuetify и список для справочников

Есть вот такой интерфейсСлева справочники, вот думаю как сделать чтобы там был список справочников, чтобы пользователь мог выбрать любой...

129
Поменять дату по нажатию на button

Поменять дату по нажатию на button

Есть график на котором можно выбирать диапазон например с 2010-1-01 по 2020-2-02Я попытался сделать чтоб по нажатию на кнопку забивалась конкретная...

143
Почему не работает onMouseLeave в react?

Почему не работает onMouseLeave в react?

Не работает onMouseLeave когда кнопка disabled, а когда enabled то работает

136