В этом примере кнопка svg-plus при нажатии превращается в кнопку закрытия и наоборот при нажатии svg.setAttribute("transform","rotate(45)");
.
Можно ли так распланировать эту трансформацию, чтобы это был не просто переход из одного состояния в другое, а плавный переход?
Вот пример:
function rotate() {
var svg = document.getElementById("svgID");
if (svg.getAttribute("transform") === "rotate(45)") {
svg.setAttribute("transform", "rotate(0)");
} else {
svg.setAttribute("transform", "rotate(45)");
}
}
#button {
margin: 0;
padding: 0;
background: none;
outline: none;
box-shadow: none;
border: none;
}
<button id = "button" onclick = "rotate()">
<svg id = "svgID"
width = "100%"
height = "100%"
viewBox = "0 0 142.40739 142.40991"
version = "1.1"
xmlns = "http://www.w3.org/2000/svg"
xmlns:svg = "http://www.w3.org/2000/svg">
<defs
id = "defs6" />
<path
id = "rect234"
style = "display:inline;opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-miterlimit:0;stroke-dasharray:none;paint-order:stroke fill markers"
d = "m 85.223081,28.929132 a 4.2333333,4.2333333 0 0 0 -4.233333,4.233333 v 62.801852 l -65.486442,-0.24339 a 4.107912,4.107912 0 0 0 -4.247803,3.96255 4.2333333,4.2333333 0 0 0 4.217313,4.249353 l 65.516932,0.24339 v 62.9295 a 4.107912,4.107912 0 0 0 3.978568,4.23333 4.2333333,4.2333333 0 0 0 4.233334,-4.23333 v -62.89901 l 60.21338,0.22376 a 4.107912,4.107912 0 0 0 4.2478,-3.96255 4.2333333,4.2333333 0 0 0 -4.21731,-4.248833 L 89.20165,95.994807 V 33.162465 a 4.107912,4.107912 0 0 0 -3.978569,-4.233333 z"
transform = "translate(-11.255471,-28.929132)" />
</svg>
</button>
преобразование SVG с помощью setAttribute("transform",rotate(45)")
:
🤔 А знаете ли вы, что...
JavaScript поддерживает работу с куки и хранилищем веб-браузера для сохранения данных на клиентской стороне.
Вам нужно использовать свойство CSS animate
.
Ниже показана односторонняя/одноразовая анимация. (после щелчка он не будет возвращаться или анимироваться дальше)
let svg = document.querySelector("#svg")
svg.addEventListener("click", () => {
if (!svg.classList.contains("animateSVG")) {
svg.classList.add("animateSVG")
}
})
#container {
position: absolute;
top: 0px;
left: 0px;
height: 100px;
width: 100px;
border: 1px solid black;
}
@keyframes frames {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(45deg);
}
}
.animateSVG {
animation-name: frames;
animation-duration: 1s;
animation-fill-mode: forwards;
}
<div id = "container">
<svg id = "svg" viewBox = "0 0 3 3" version = "1.1" xmlns = "http://www.w3.org/2000/svg" xmlns:svg = "http://www.w3.org/2000/svg">
<path style = "opacity: 0.468379; fill: none; stroke: #000000; stroke-width: 0.264583; stroke-linecap: round; stroke-linejoin: round;" d = "M0.5,1.5h2zM1.5,0.5v2z" />
</svg>
</div>
Способ 1: requestAnimationFrame
function anim(from, to, cb, duration=100) {
let startms;
const frame = ms => {
const x = (ms - startms) ;
if (x > duration) {
return cb(to);
}
cb(x / duration * (to - from) + from)
requestAnimationFrame(frame);
}
requestAnimationFrame(ms => {
startms = ms;
requestAnimationFrame(frame);
})
}
function rotate() {
var svg = document.getElementById("svgID");
if (svg.getAttribute("transform") === "rotate(45)") {
anim(45, 0, v=> svg.setAttribute("transform", `rotate(${v})`));
} else {
anim(0, 45, v=> svg.setAttribute("transform", `rotate(${v})`));
}
}
#button {
margin: 0;
padding: 0;
background: none;
outline: none;
box-shadow: none;
border: none;
}
<button id = "button" onclick = "rotate()">
<svg id = "svgID"
width = "100%"
height = "100%"
viewBox = "0 0 142.40739 142.40991"
version = "1.1"
xmlns = "http://www.w3.org/2000/svg"
xmlns:svg = "http://www.w3.org/2000/svg">
<defs
id = "defs6" />
<path
id = "rect234"
style = "display:inline;opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-miterlimit:0;stroke-dasharray:none;paint-order:stroke fill markers"
d = "m 85.223081,28.929132 a 4.2333333,4.2333333 0 0 0 -4.233333,4.233333 v 62.801852 l -65.486442,-0.24339 a 4.107912,4.107912 0 0 0 -4.247803,3.96255 4.2333333,4.2333333 0 0 0 4.217313,4.249353 l 65.516932,0.24339 v 62.9295 a 4.107912,4.107912 0 0 0 3.978568,4.23333 4.2333333,4.2333333 0 0 0 4.233334,-4.23333 v -62.89901 l 60.21338,0.22376 a 4.107912,4.107912 0 0 0 4.2478,-3.96255 4.2333333,4.2333333 0 0 0 -4.21731,-4.248833 L 89.20165,95.994807 V 33.162465 a 4.107912,4.107912 0 0 0 -3.978569,-4.233333 z"
transform = "translate(-11.255471,-28.929132)" />
</svg>
</button>
Способ 2: анимировать
function anim(el, from, to, duration=100) {
const animation = [
{ transform: `rotate(${from}deg)` },
{ transform: `rotate(${to}deg)` }
];
const timing = {
fill: 'both',
duration,
iterations: 1
};
el.animate(animation, timing);
}
function rotate() {
var svg = document.getElementById("svgID");
if (svg.dataset.rotated === "1") {
svg.dataset.rotated = "0";
anim(svg, 45, 0);
} else {
svg.dataset.rotated = "1";
anim(svg, 0, 45);
}
}
#button {
margin: 0;
padding: 0;
background: none;
outline: none;
box-shadow: none;
border: none;
}
<button id = "button" onclick = "rotate()">
<svg id = "svgID"
width = "100%"
height = "100%"
viewBox = "0 0 142.40739 142.40991"
version = "1.1"
xmlns = "http://www.w3.org/2000/svg"
xmlns:svg = "http://www.w3.org/2000/svg">
<defs
id = "defs6" />
<path
id = "rect234"
style = "display:inline;opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-miterlimit:0;stroke-dasharray:none;paint-order:stroke fill markers"
d = "m 85.223081,28.929132 a 4.2333333,4.2333333 0 0 0 -4.233333,4.233333 v 62.801852 l -65.486442,-0.24339 a 4.107912,4.107912 0 0 0 -4.247803,3.96255 4.2333333,4.2333333 0 0 0 4.217313,4.249353 l 65.516932,0.24339 v 62.9295 a 4.107912,4.107912 0 0 0 3.978568,4.23333 4.2333333,4.2333333 0 0 0 4.233334,-4.23333 v -62.89901 l 60.21338,0.22376 a 4.107912,4.107912 0 0 0 4.2478,-3.96255 4.2333333,4.2333333 0 0 0 -4.21731,-4.248833 L 89.20165,95.994807 V 33.162465 a 4.107912,4.107912 0 0 0 -3.978569,-4.233333 z"
transform = "translate(-11.255471,-28.929132)" />
</svg>
</button>
В этом конкретном случае (переключение между двумя состояниями) вы также можете рассмотреть более традиционный подход:
let btns = document.querySelectorAll('.buttonClose');
btns.forEach(btn=>{
btn.addEventListener('click', (e)=>{
btn.classList.toggle('expanded');
})
})
body{
font-size: 20em
}
.button {
margin: 0;
padding: 0;
background: none;
outline: none;
box-shadow: none;
border: 1px solid #000;
width:1em;
font-size:1em;
line-height:0.5em;
padding:0;
margin:0;
}
.closeIcon{
transform-origin: center;
transition: 0.3s transform ease-in-out;
fill: currentColor;
height:100%;
width:auto;
}
.expanded .closeIcon{
transform: rotate(45deg)
}
.button2{
color:red;
font-size:0.5em;
}
<button class = "button buttonClose">
<svg class = "closeIcon" viewBox = "0 0 143 143">
<path d = "M74.25 0.5 a4.23 4.23 0 00-4.23 4.23v62.8l-65.49-.24a4.11 4.11 0 00-4.24 3.96 4.23 4.23 0 004.21 4.25l65.52 .25v62.93a4.11 4.11 0 003.98 4.23 4.23 4.23 0 004.23-4.23v-62.9l60.22 .22a4.11 4.11 0 004.24-3.96 4.23 4.23 0 00-4.21-4.25l-60.25-.23v-62.83a4.11 4.11 0 00-3.98-4.23z" />
</svg>
</button>
<button class = "button buttonClose button2">
<svg class = "closeIcon" viewBox = "0 0 143 143">
<path d = "M74.25 0.5 a4.23 4.23 0 00-4.23 4.23v62.8l-65.49-.24a4.11 4.11 0 00-4.24 3.96 4.23 4.23 0 004.21 4.25l65.52 .25v62.93a4.11 4.11 0 003.98 4.23 4.23 4.23 0 004.23-4.23v-62.9l60.22 .22a4.11 4.11 0 004.24-3.96 4.23 4.23 0 00-4.21-4.25l-60.25-.23v-62.83a4.11 4.11 0 00-3.98-4.23z" />
</svg>
</button>
Оптимизируя данные пути, вы также можете избавиться от некоторых ненужных преобразований, упростив поворотный переход.
Вы можете использовать, например, svg-path-editor, чтобы уменьшить точность с плавающей запятой и центрировать путь в viewBox
. Таким образом, мы можем опустить атрибут transform = "translate(-11.255471,-28.929132)"
, поскольку мы можем изменить положение данных относительного пути, изменив первые координаты M
.
Нам также не нужны эти атрибуты:
version = "1.1"
=> ... совершенно бесполезно, возможно, требуется валдаторамиxmlns = "http://www.w3.org/2000/svg"
=> необходимо для SVG с внешними ссылками, но не требуется для встроенных элементов SVGКроме того, мы можем/должны удалить атрибут встроенного стиля, поскольку он излишне усложняет стилизацию (например, изменение цвета, изменение размера).
display:inline
=> не влияет на элементы SVGopacity:1; fill:#000000; fill-opacity:1
=> значения по умолчанию — можно безопасно удалитьstroke:#000000;stroke-width:0;stroke-linecap:round;stroke-miterlimit:0;stroke-dasharray:none
=> stroke-width:0
отключает все обводки — последующие свойства не влияют на рендерингИспользование имен классов вместо идентификаторов помогает повторно использовать кнопки в разных стилях.
<animateTransform>
Анимация, CSS не нужен.Оберните его в собственный веб-компонент JavaScript <svg-rotate-icon>
для еще большего удобства использования.
Затем один HTML-тег создает один интерактивный значок.
<svg-rotate-icon id=ONE></svg-rotate-icon>
<svg-rotate-icon id=TWO></svg-rotate-icon>
<svg-rotate-icon id=THREE rotated></svg-rotate-icon>
<svg-rotate-icon id=FOUR disabled></svg-rotate-icon>
Обратите внимание на viewBox = "-50 -50 100 100"
, который делает начало координат 0,0 центром SVG, что упрощает вращение.
<style>
svg-rotate-icon { background:beige; width: 100px }
svg-rotate-icon[rotated]{ background:green }
</style>
<script>
customElements.define("svg-rotate-icon", class extends HTMLElement {
connectedCallback() {
let rotated = this.hasAttribute("rotated");
let transform = `transform=rotate(${rotated ? 45 : 0})`;
this
.attachShadow({mode:"open"}) // required for use of :host and :host(), up to you to deleted it
.innerHTML = `<style>:host{display:inline-block;cursor:pointer}</style>` +
`<style>:host([disabled]){opacity:.5;pointer-events:none}</style>` +
`<svg viewBox = "-50 -50 100 100">` +
` <path d = "M-35 0h70M0-35v70" ${transform} stroke=black stroke-width=10 stroke-linecap=round>` +
` <animateTransform attributeName=transform type=rotate dur=300ms begin=indefinite fill=freeze />` +
` </path>` +
`</svg>`;
let animElement = this.shadowRoot.querySelector("animateTransform");
this.onclick = (evt) => {
animElement.setAttribute('from', rotated ? 45 : 0);
animElement.setAttribute('to' , rotated ? 0 : 45);
animElement.beginElement();
}
animElement.onend = (evt) => {
this.toggleAttribute("rotated",rotated = !rotated);
console.info(`rotated ${this.id}`, "dispatch Event here (with bubbles/composed properties)")
}
}
})
</script>
<svg-rotate-icon id=ONE></svg-rotate-icon>
<svg-rotate-icon id=TWO></svg-rotate-icon>
<svg-rotate-icon id=THREE rotated></svg-rotate-icon>
<svg-rotate-icon id=FOUR disabled></svg-rotate-icon>