#javascript #svg #events
Вопрос:
Я создаю игру, используя PIXI для графики и Мифрил для некоторых элементов пользовательского интерфейса. Я создаю прослушиватели событий для различных входов (WASD для движения, щелчки мыши для действия). Я визуализирую SVG как часть пользовательского интерфейса (с тегом <объект>), и когда я нажимаю на этот SVG, в браузере должна быть некоторая «переориентация», так как ключевые входные данные прекращают запуск событий. В частности, я даже не могу использовать клавишу, которая должна включать/выключать SVG!
Я не могу просто отключить взаимодействие с элементом, так как мне нужно иметь возможность подключать прослушиватели событий к определенным элементам в SVG. Как мне исправить это поведение «переориентации»? Как ни странно, я также не могу запустить onclick при нажатии svg (после передачи функции onclick элементу <объект>).
Ответ №1:
Ваш SVG, загружаемый в элемент <object>, как и в <object><iframe>, имеет свой собственный документ, который изолирован от документа владельца.
События, происходящие в одном контексте, не будут видны другому. Это специально, вы бы не хотели, чтобы случайное объявление на странице могло читать все, что напечатано в его встраивателе, и аналогично другим способом.
Поэтому здесь простое решение состоит в том, чтобы добавить ваш SVG в качестве встроенного элемента непосредственно в основной документ:
onkeydown = (evt) => {
evt.preventDefault();
console.log( "pressed", evt.key );
}
Press any key or click on the rect<br>
<svg viewBox="0 0 100 100" width="100">
<rect fill="green" width="100" height="100"/>
<script>
document.querySelector("rect")
.addEventListener("click", (evt) =>
evt.target.setAttribute("fill", "#" ((Math.random()*0xFFFFF)|0) )
);
</script>
</svg>
Если ваше svg-изображение и ваш документ имеют одинаковое происхождение, вы можете получить доступ к <объекту><объекту> .contentDocument
и прикрепить события туда даже из основного документа.
document.querySelector("object").addEventListener("load", (evt) => {
const doc = evt.target.contentDocument;
// add your event here
doc.addEventListener("keydown", handler);
});
Пример аутсорсинга, потому что iframes StackSnippet с нулевым происхождением не могут быть одного и того же происхождения…
Для документов перекрестного происхождения более сложным способом было бы создать узел между обоими контекстами и сообщить другой стороне, когда произошло событие.
Есть предложение, которое должно позволить это, хотя оно уже давно не обновлялось, и оно скорее нацелено на использование OffscreenCanvas в веб-работниках в качестве варианта использования. Но то, как все происходило в последнем обновлении, позволяло надеяться, что с этой ситуацией тоже можно будет справиться.
Я написал хакерскую реализацию, основанную на этом последнем предложении, вот пример использования:
const svg_content = `
<svg viewBox="0 0 100 100" width="100" xmlns="http://www.w3.org/2000/svg">
<rect fill="green" width="100" height="100"/>
<!-- load the script from both sides
note that here we load the "cross-origin" version,
because StackSnippet's null-origined iframes make this object cross-origin...
-->
<script href="https://cdn.jsdelivr.net/gh/Kaiido/EventPort.js@cross-origin/EventPort.js"/>
<script>
document.querySelector("rect")
.addEventListener("click", (evt) =>
evt.target.setAttribute("fill", "#" ((Math.random()*0xFFFFF)|0) )
);
// "parent" will listen to Events firing on "window"
const eventPort = window.createEventPort();
parent.postMessage( eventPort, "*", [eventPort] );
</script>
</svg>
`;
document.querySelector("object")
.data = URL.createObjectURL( new Blob( [ svg_content ], { type: "image/svg xml" } ) );
// we wait for the SVG document to send our EventPort object
window.addEventListener("message", (evt) => {
const eventPort = evt.eventPorts[ 0 ];
// now we can listen to the events occuring there
eventPort.addEventListener( "keydown", (evt) => {
console.log( "pressed in svg document", evt.key );
});
eventPort.addEventListener( "click", (evt) => {
console.log( "clicked in svg document" );
});
});
addEventListener("keydown", (evt) => {
console.log( "pressed in main document", evt.key );
})
<!-- load the script from both sides -->
<script src="https://kaiido.github.io/EventPort.js/EventPort.js"></script>
Press any key or click on the rect<br>
<object></object>
Но на самом деле мой сценарий является хакерским, я написал это только в качестве доказательства концепции, чтобы помочь лучше разработать возможный API, и поскольку он перезаписывает множество глобальных файлов, он вполне может сломать другие материалы на вашей странице.
Так что, если вы можете, идите простым путем: встроите свой svg.
Комментарии:
1. Поскольку я использую Мифрил для управления состоянием пользовательского интерфейса, я не хочу много встраивать в основной документ. Благодаря вашему ответу мне удалось частично решить свои проблемы, вызвав document.body.focus() в прослушивателе событий щелчка на элементе svg, что приводит к немедленному переключению фокуса при щелчке, однако мне придется регистрировать аналогичные обработчики для каждого события, которое меняет фокус. Знаете ли вы, можно ли в первую очередь предотвратить изменение фокуса, но при этом разрешить обработку щелчков и так далее? Я полагаю, что нет, и в этом случае, знаете ли вы более простой способ сделать то, что я описал?
2. Вам абсолютно необходимо, чтобы событие click в svg находилось на заполненной части элементов или где-либо в svg было бы нормально? Если последнее, то используйте <img> для визуализации вашего svg.
3. Я использую svg специально, потому что хотел иметь возможность прикреплять разных слушателей к разным частям изображения.
4. У вас все еще есть элемент <карта><карта> , в противном случае, я думаю, я дал все, что мог придумать в своем ответе.