Управление SVG-координатами `path` с помощью JavaScript
104
01 октября 2021, 08:10
У меня есть path SVG со следующими точками в атрибуте d.
Есть ли способ изменять только одно из координатных чисел в 'd', скажем, как 0 в "L25 0", чтобы манипулировать в сценарии JavaScript?
Если это возможно, то как бы выглядел этот синтаксис?
Я хочу получить доступ к одному из координатных чисел, чтобы я мог увеличить его, и тем самым анимировать path.
Свободный перевод вопроса Manipulating SVG Path Coordinate with Vanilla JavaScript от участника @Nhan Bui.
Answer 1
Часть 2
Стандартный JavaScript
Упростить работу с отдельными значениями координат path можно и на чистом JS, без зависимостей. Например, можно добавить недостающее DOM-свойство d, и перехватывать обращения к нему используя Proxy (ES6+).
Следующий пример демонстрирует визуализацию частот во время воспроизведения звукового файла. Отрисовка линии выполняется через path (130 точек):
function attributesSerializer(element, attributes){returnnewProxy(element,{get(el, prop){if(prop ==='$attrs')returnthis.$attrs;if(this.$attrs[prop])returnthis.$attrs[prop];const val = el[prop];return(typeof val ==='function')? val.bind(el): val;},set(el, prop, val){if(prop in this.$attrs)returnfalse;return((el[prop]= val),true);},
$attrs: attributes.reduce((r,{ attr, delim=' '})=>{
r[attr]=newProxy({},{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));returntrue;}});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 =newAudioContext(),
src = ac.createMediaElementSource(audio);
analyser = ac.createAnalyser();
src.connect(analyser);
analyser.connect(ac.destination);
bufferLen =(analyser.fftSize =512)/2;
freqData =newUint8Array(bufferLen);
audio.play();
loop();});function loop(){
analyser.getByteFrequencyData(freqData);
path.d[0]=`M0,1000,${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);}
Здесь функция attributesSerializer возвращает экземпляр Proxy, привязанный к DOM-объекту элемента path.
Этим прокси мы ловим только обращения к добавленному свойству d - которое так же отслеживается через прокси (вложенный), что позволяет считывать и присваивать значения отдельных точек в одноименном атрибуте через квадратноскобочную нотацию вида pathElement.d[индексТочки].
В качестве символа-разделителя для (де)сериализации атрибута используется символ \n.
Такой подход обладает рядом недостатков. Основными минусами реализации являются ее относительная сложность, низкое быстродействие, и то что она является "велосипедом".
Поэтому, для решения таких задач, всегда лучше использовать реактивность. Ведь инструменты которые ее реализуют, предоставляют также массу других возможностей, помимо работы с атрибутами SVG.
Answer 2
На самом деле нет никакого основанного на DOM способа прямого доступа и манипулирования только одной из точек в определении пути.
Манипулирование строками - это все, что вы можете сделать.
functionNavHalf(x,y){var arrow = document.getElementById("arrowUp");
arrow.setAttribute('d','M0 0 L'+ x +','+ y +' L50 0');}
Свободный перевод ответа от участника @Paul LeBeau.
Answer 3
Часть 1
Так как значение отдельной координаты здесь не является объектом/свойством DOM, прямые манипуляции с этим значением в JS невозможны - вместо этого мы манипулируем атрибутом, изменяя его строковое значение.
И, существует...
Типовой подход - реактивные библиотеки, фреймворки
(только критика, без примеров)
Сейчас уже мало кто пишет голый JS для решения подобных задач: распространена практика использования реактивных библиотек, чтобы упростить работу - и писать меньше кода, тем самым облегчая его поддержку.
Ну, так это должно работать...
Посмотрим как это выглядит для начинающего:
Для начала, нужно выбранную библиотеку подключить. Или две библиотеки, в случае реакта. Теперь страничка грузится чуть дольше. Хм, ну ладно, вроде мелочь - можно потерпеть ради преимуществ.
Далее надо написать бойлерплейт-код, в который поместим ту логику связи с DOM, которую мы хотим. Тут уже возникает смутное ощущение, что... а, неважно. Напишем. Мыжпрограммисты, в конце концов.
Но как-то это не очень удобно, в разметке набирать. Перенесем код в отдельный модуль - и бандлером будем все вместе собирать. Вебпак, то что нужно! Поглядим как его настраив... оооо... нет, НЕТ, мы не будем его настраивать. Все ж работает, да? Вот и хорошо, вот и славно.
Чтобы быстрее оправиться от психической травмы нанесенной вебпаком, изучим и применим TS - не надо будет сильно думать о логике выражений с типкастом. В самом деле, пусть машина думает, а нам и бойлерплейтов хватает... ведь мы уже по уши в фреймворке.
Вышло так, что в попытке упростить свою работу, мы ее усложняем.
Инструменты, призванные помочь сконцентрироваться на программировании, уводят все дальше от программирования: мы npm install половину задач, и зависимы от внутренней магии готовых решений.
Экономия времени и совершенствование библиотек это в целом хорошо, если бы разработчики не забывали язык(!) и не теряли гибкость ума, попадая в ловушку бездумного потребления.
Многобукв, но зачем? Просто этим подвожу к альтернативному варианту, который вдохновляет лично меня. К варианту, который хотя бы частично вернет мотивацию вникать, понимать, развивать фундаментальные навыки - и к отказу от "жизни в фреймворке", в пользу реализации реактивности на уровне языка.
Этот вариант существует больше на уровне идеи, но есть несколько первых попыток ее воплотить. Здесь упомяну только одну молодую реализацию, а именно...