More HTML/JavaScript graphics experimentation, this time with SVG animation. Click/tap and drag in the frame below.
Source:
<svg id="mySvg" viewBox="0 0 400 300" style="border: solid 1px black;
width:100%; aspect-ratio: 4/3"></svg>
<script>
var svg = document.getElementById("mySvg"),
svgViewRect, clientViewRect, // svg and client coordinates
currMouseDown = null,
mouseDownIdleID = 0;
const initialMouseTimeout = 250, fasterMouseTimeout = 25;
function animate(element, attrName, to, dur, repeatCount, fill = null) {
// animates SVG element attribute from its current value towards "to" value over timespan "dur"
var animation = document.createElementNS("http://www.w3.org/2000/svg", "animate");
animation.setAttribute("attributeName", attrName);
animation.setAttribute("to", to);
animation.setAttribute("dur", dur);
animation.setAttribute("repeatCount", repeatCount);
if (fill) animation.setAttribute("fill", fill);
animation.setAttribute("begin", svg.getCurrentTime());
element.appendChild(animation);
}
function blurListener(event) {
if (currMouseDown) {
clearTimeout(mouseDownIdleID);
currMouseDown = null;
mouseDownIdleID = 0;
}
}
function clickListener(event) {
}
function clientToSVGcoords({ x, y }) {
return {
x: (svgViewRect.width * (arguments[0].x - clientViewRect.left)) / clientViewRect.width,
y: (svgViewRect.height * (arguments[0].y - clientViewRect.top)) / clientViewRect.height
};
}
function containerResized() {
svgCoordinatesChanged();
}
function focusListener(event) {
}
function loadListener(event) {
}
function mouseDownIdle() {
// perform this action while mouse is held down in same spot for longer than initialMouseTimeout
randomCircle(clientToSVGcoords({ x: currMouseDown.clientX, y: currMouseDown.clientY }));
mouseDownIdleID = setTimeout(mouseDownIdle, fasterMouseTimeout); // repeat faster after initial timeout
}
function mouseDownListener(event) {
if (event.buttons & 1) { // if left click...
svg.focus(); // note: svg.focus to generate the event, not focusListener()
currMouseDown = event;
mouseDownIdleID = setTimeout(mouseDownIdle, initialMouseTimeout);
randomCircle(clientToSVGcoords({ x: event.clientX, y: event.clientY }))
event.preventDefault();
event.stopPropagation();
} else {
// do something for other buttons?
}
}
function mouseMoveListener(event) {
// listen for this outside element; it may be part of a mousedown started inside
if (currMouseDown) {
randomCircle(clientToSVGcoords({ x: event.clientX, y: event.clientY }));
clearTimeout(mouseDownIdleID);
currMouseDown = event; // update current mousedown position and reset idle timout
mouseDownIdleID = setTimeout(mouseDownIdle, initialMouseTimeout);
event.preventDefault();
event.stopPropagation();
// prevent default, otherwise mousing down out of element may select neighboring content
}
}
function mouseUpListener(event) {
// listen for this outside element; it may be part of a mousedown started inside
if (currMouseDown) {
clearTimeout(mouseDownIdleID);
currMouseDown = null;
mouseDownIdleID = 0;
}
}
function randomCircle({ x, y }, rMin = 15, rMax = 40, dur = 5) {
// creates a randomly sized and colored SVG circle at x,y moving in a random direction
// opacity decreasing with duration "dur" in seconds
// circle is removed by a timeout callback at the end of that time, when completely transparent
var circle, r;
r = (rMax - rMin) * Math.random() + rMin;
circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
circle.setAttribute("cx", arguments[0].x);
circle.setAttribute("cy", arguments[0].y);
circle.setAttribute("r", r);
circle.setAttribute("fill", "#" + (((1 << 24) * Math.random()) | 0).toString(16));
svg.appendChild(circle);
animate(circle, "cx", Math.random() * svgViewRect.width + svgViewRect.x, `${dur}s`, "indefinite");
animate(circle, "cy", Math.random() * svgViewRect.height + svgViewRect.y, `${dur}s`, "indefinite");
animate(circle, "opacity", "0", `${dur}s`, "1", "freeze");
setTimeout(function () { svg.removeChild(circle) }, dur * 1000);
return circle;
}
function scrollResizeListener(event) {
svgCoordinatesChanged();
}
function svgCoordinatesChanged() {
let viewBoxArray = svg.getAttribute("viewBox").split(" ");
svgViewRect = new DOMRect(viewBoxArray[0], viewBoxArray[1], viewBoxArray[2], viewBoxArray[3]);
clientViewRect = svg.getBoundingClientRect(); // includes padding and border, excludes margin
// convert to rectangle of content area, excluding padding and border:
let style = getComputedStyle(svg);
let leftOffset = parseInt(style.paddingLeft) + parseInt(style.borderLeft);
let topOffset = parseInt(style.paddingTop) + parseInt(style.borderTop);
clientViewRect.x += leftOffset;
clientViewRect.y += topOffset;
clientViewRect.width -= leftOffset + parseInt(style.paddingRight) + parseInt(style.borderRight);
clientViewRect.height -= topOffset + parseInt(style.paddingBottom) + parseInt(style.borderBottom);
}
function touchCancelEndListener(event) {
// listen for this outside element; it may be part of a mousedown started inside
if (currMouseDown) {
clearTimeout(mouseDownIdleID);
currMouseDown = null;
mouseDownIdleID = 0;
event.preventDefault();
event.stopPropagation();
}
}
function touchMoveListener(event) {
if (currMouseDown) {
currMouseDown = event;
for (var i = 0, t = event.touches; i < t.length; ++i) {
randomCircle(clientToSVGcoords({ x: t.item(i).clientX, y: t.item(i).clientY }));
}
event.preventDefault();
event.stopPropagation();
}
}
function touchStartListener(event) {
svg.focus();
currMouseDown = event;
for (var i = 0, t = event.touches; i < t.length; ++i) {
randomCircle(clientToSVGcoords({ x: t.item(i).clientX, y: t.item(i).clientY }));
}
event.preventDefault();
event.stopPropagation();
}
function initialize() {
window.addEventListener("mousemove", mouseMoveListener);
window.addEventListener("mouseup", mouseUpListener);
window.addEventListener("scroll", scrollResizeListener);
window.addEventListener("resize", scrollResizeListener);
window.addEventListener("touchmove", touchMoveListener);
window.addEventListener("touchend", touchCancelEndListener);
window.addEventListener("touchcancel", touchCancelEndListener);
svg.addEventListener("focus", focusListener);
svg.addEventListener("blur", blurListener);
svg.addEventListener("load", loadListener);
svg.addEventListener("mousedown", mouseDownListener);
svg.addEventListener("touchstart", touchStartListener);
svg.addEventListener("click", clickListener);
// click happens after mouseup over same element where mousedown occured
// neighboring elements may affect svg size/position, so monitor common container
new ResizeObserver(containerResized).observe(svg.parentNode);
}
initialize();
</script>
Comments
Post a Comment