На многих сайтах стали появляться эффекты вращения окружностей с симметрично вырезанными небольшими участками. Смотрится хорошо. Как повторить данный эффект?
У меня получилось вырезать один сегмент с помощью атрибутов 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>
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>
Сегменты расположены вертикально
.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>
Современные решения для бизнеса: как облачные и виртуальные технологии меняют рынок
Виртуальный выделенный сервер (VDS) становится отличным выбором
Возникла такая трудность вывожу на экран(в canvas html5) массив объектов, и мне как то надо задать этим объектам разную скорость вращенияКак это...
Есть вот такой интерфейсСлева справочники, вот думаю как сделать чтобы там был список справочников, чтобы пользователь мог выбрать любой...
Есть график на котором можно выбирать диапазон например с 2010-1-01 по 2020-2-02Я попытался сделать чтоб по нажатию на кнопку забивалась конкретная...
Не работает onMouseLeave когда кнопка disabled, а когда enabled то работает