На многих сайтах стали появляться эффекты вращения окружностей с симметрично вырезанными небольшими участками. Смотрится хорошо. Как повторить данный эффект?
У меня получилось вырезать один сегмент с помощью атрибутов 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 г.
Выполнены все непростые условия конкурса!
Я уверен, что многие люди со временем будут возвращаться вновь и вновь в этот топик, чтобы использовать оригинальные решения автора ответа. Весь код очень подробно и доступно прокомментирован, что облегчает восприятие и освоение интересных комбинаций различных техник решения.
Сразу оговорюсь, что 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"> SPECIAL OPERATIONS </label>
Создание можно разделить на несколько этапов:
<div class="spin"></div>;.spin, .spin:after {} и добавляем следующие свойства:
width и height - указываем одинаковые значения для ширины и высоты;box-sizing: border-box; - чтобы толщина рамки не влияла на окружающие элементы;border: 3px solid grey; - собственно, рамка с указанием толщины;border-radius: 50%; - скругляем углы элемента, превращая т.о. в круг;border-color: grey transparent grey transparent;..spin {} со свойствами:
position: relative; - для правильного позиционирования псевдоэлемента;transform: rotate(0deg); - угол поворота в исходном положении блока;transition: 1.5s ease; - задаём переход, чтобы происходила плавная анимация вращения, а не резкий скачок от начального положения к конечному.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"> SPECIAL OPERATIONS </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"> SPECIAL OPERATIONS </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"> SPECIAL OPERATIONS </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>
20pxC = 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>
Сегменты расположены вертикально
.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>
Также используем атрибут 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>
От меня, как и в прошлый раз 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>
Основные этапы разработки сайта для стоматологической клиники
Продвижение своими сайтами как стратегия роста и независимости