У меня есть path
SVG со следующими точками в атрибуте d
.
Есть ли способ изменять только одно из координатных чисел в 'd', скажем, как 0 в "L25 0"
, чтобы манипулировать в сценарии JavaScript?
Если это возможно, то как бы выглядел этот синтаксис?
Я хочу получить доступ к одному из координатных чисел, чтобы я мог увеличить его, и тем самым анимировать path
.
function NavHalf() {
var arrow = document.getElementById("arrowUp");
arrow.setAttribute('d', 'M0 25 L25 0 L50 25');
}
<svg height="25" width="50" onclick="NavHalf()">
<path id="arrowUp" d="M0 0 L25 25 L50 0 " style="stroke:green;stroke-width:2; fill:none" />
</svg>
Свободный перевод вопроса Manipulating SVG Path Coordinate with Vanilla JavaScript от участника @Nhan Bui.
Часть 2
Упростить работу с отдельными значениями координат path
можно и на чистом JS, без зависимостей. Например, можно добавить недостающее DOM-свойство d
, и перехватывать обращения к нему используя Proxy
(ES6+).
Следующий пример демонстрирует визуализацию частот во время воспроизведения звукового файла. Отрисовка линии выполняется через path
(130 точек):
function attributesSerializer(element, attributes) {
return new Proxy(element, {
get(el, prop) {
if (prop === '$attrs') return this.$attrs;
if (this.$attrs[prop]) return this.$attrs[prop];
const val = el[prop];
return (typeof val === 'function') ? val.bind(el) : val;
},
set(el, prop, val) {
if (prop in this.$attrs) return false;
return ((el[prop] = val), true);
},
$attrs: attributes.reduce((r, { attr, delim=' ' }) => {
r[attr] = new Proxy({}, {
get(obj, i) {
const values = element.getAttribute(attr).split(delim).map(v => v.trim());
return values[i];
},
set(obj, i, newVal) {
const values = element.getAttribute(attr).split(delim).map(v => v.trim());
values[i] = newVal;
element.setAttribute(attr, values.join(delim));
return true;
}
});
return r;
}, {})
});
}
const path = attributesSerializer(document.querySelector('path'), [
{ attr: 'd', delim: '\n' }
]);
let analyser, bufferLen, freqData;
document.querySelector('input').addEventListener('change', e => {
const audio = document.querySelector('audio');
audio.src = URL.createObjectURL(e.target.files[0]);
audio.load();
const ac = new AudioContext(),
src = ac.createMediaElementSource(audio);
analyser = ac.createAnalyser();
src.connect(analyser);
analyser.connect(ac.destination);
bufferLen = (analyser.fftSize = 512) / 2;
freqData = new Uint8Array(bufferLen);
audio.play();
loop();
});
function loop() {
analyser.getByteFrequencyData(freqData);
path.d[0] = `M0,100 0,${100 - freqData[0] / 2.55}`;
for (var i = 2; i < bufferLen; i += 2)
path.d[1 + i / 2] = `${100 * i / bufferLen},${100 - freqData[i] / 2.55}`;
path.d[i] = 'L100,100 Z';
requestAnimationFrame(loop);
}
body { display: flex; flex-flow: column nowrap; align-items: stretch; height: calc(100% - 32px); }
svg { align-self: center; width: 100%; max-width: 400px; height: 100px; outline: 1px dashed #ccc; }
input { margin: 0.5rem 0; }
audio { width: 100%; outline: none; }
input:invalid + audio { display: none; }
<svg viewBox="0 0 80 100" preserveAspectRatio="none">
<path d="" fill="#def" stroke="#00f" stroke-width="0.1" />
</svg>
<input type="file" accept="audio/*" required>
<audio controls></audio>
Здесь функция attributesSerializer
возвращает экземпляр Proxy
, привязанный к DOM-объекту элемента path
.
Этим прокси мы ловим только обращения к добавленному свойству d
- которое так же отслеживается через прокси (вложенный), что позволяет считывать и присваивать значения отдельных точек в одноименном атрибуте через квадратноскобочную нотацию вида pathElement.d[индексТочки]
.
В качестве символа-разделителя для (де)сериализации атрибута используется символ \n
.
Такой подход обладает рядом недостатков. Основными минусами реализации являются ее относительная сложность, низкое быстродействие, и то что она является "велосипедом".
Поэтому, для решения таких задач, всегда лучше использовать реактивность. Ведь инструменты которые ее реализуют, предоставляют также массу других возможностей, помимо работы с атрибутами SVG.
На самом деле нет никакого основанного на DOM способа прямого доступа и манипулирования только одной из точек в определении пути.
Манипулирование строками - это все, что вы можете сделать.
function NavHalf(x,y) {
var arrow = document.getElementById("arrowUp");
arrow.setAttribute('d', 'M0 0 L' + x + ',' + y + ' L50 0');
}
<button type="button" onclick="NavHalf(25,0)">Position 1</button>
<button type="button" onclick="NavHalf(20,15)">Position 2</button>
<button type="button" onclick="NavHalf(25,25)">Position 3</button>
<svg height="25" width="50">
<path id="arrowUp" d="M0 0 L25 25 L50 0" style="stroke:green;stroke-width:2; fill:none" />
</svg>
Свободный перевод ответа от участника @Paul LeBeau.
Часть 1
Так как значение отдельной координаты здесь не является объектом/свойством DOM, прямые манипуляции с этим значением в JS невозможны - вместо этого мы манипулируем атрибутом, изменяя его строковое значение.
И, существует...
(только критика, без примеров)
Сейчас уже мало кто пишет голый JS для решения подобных задач: распространена практика использования реактивных библиотек, чтобы упростить работу - и писать меньше кода, тем самым облегчая его поддержку.
Ну, так это должно работать...
Посмотрим как это выглядит для начинающего:
Для начала, нужно выбранную библиотеку подключить. Или две библиотеки, в случае реакта. Теперь страничка грузится чуть дольше. Хм, ну ладно, вроде мелочь - можно потерпеть ради преимуществ.
Далее надо написать бойлерплейт-код, в который поместим ту логику связи с DOM, которую мы хотим. Тут уже возникает смутное ощущение, что... а, неважно. Напишем. Мыжпрограммисты, в конце концов.
Но как-то это не очень удобно, в разметке набирать. Перенесем код в отдельный модуль - и бандлером будем все вместе собирать. Вебпак, то что нужно! Поглядим как его настраив... оооо... нет, НЕТ, мы не будем его настраивать. Все ж работает, да? Вот и хорошо, вот и славно.
Чтобы быстрее оправиться от психической травмы нанесенной вебпаком, изучим и применим TS - не надо будет сильно думать о логике выражений с типкастом. В самом деле, пусть машина думает, а нам и бойлерплейтов хватает... ведь мы уже по уши в фреймворке.
Вышло так, что в попытке упростить свою работу, мы ее усложняем.
Инструменты, призванные помочь сконцентрироваться на программировании, уводят все дальше от программирования: мы npm install половину задач, и зависимы от внутренней магии готовых решений.
Экономия времени и совершенствование библиотек это в целом хорошо, если бы разработчики не забывали язык(!) и не теряли гибкость ума, попадая в ловушку бездумного потребления.
Многобукв, но зачем? Просто этим подвожу к альтернативному варианту, который вдохновляет лично меня. К варианту, который хотя бы частично вернет мотивацию вникать, понимать, развивать фундаментальные навыки - и к отказу от "жизни в фреймворке", в пользу реализации реактивности на уровне языка.
Этот вариант существует больше на уровне идеи, но есть несколько первых попыток ее воплотить. Здесь упомяну только одну молодую реализацию, а именно...
<svg height="25" width="50" on:click={toggle}>
<path d="M0 0 L25 {$midY} L50 0" style="stroke:green; stroke-width:2; fill:none;" />
</svg>
<script>
import { tweened } from 'svelte/motion';
import { cubicOut } from 'svelte/easing';
const midY = tweened(0, { duration: 250, easing: cubicOut });
const toggle = () => midY.set($midY ? 0 : 25);
</script>
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Есть задачаНаписать функцию, которая принимает время (часы, минуты, секунды) и выводит его на экран в формате «чч:мм:сс»
Как обратиться к вложенной функции и вложенной в нее функции?
Возможно ли в JavaScript округлять числа по следующей схеме: