Skip to content

Instantly share code, notes, and snippets.

@aumouvantsillage
Created November 18, 2010 13:13
Show Gist options
  • Save aumouvantsillage/704953 to your computer and use it in GitHub Desktop.
Save aumouvantsillage/704953 to your computer and use it in GitHub Desktop.
How Sozi will handle transitions along a path
Display the source blob
Display the rendered blob
Raw
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg">
<g id="container">
<rect id="source-frame" x="10" y="10" width="50" height="30" stroke="green" fill="#ada" fill-opacity="0.5" transform="translate(70, 100) scale(4) rotate(30)" />
<rect id="target-frame" x="10" y="10" width="50" height="30" stroke="red" fill="#daa" fill-opacity="0.5" transform="translate(400, 500) scale(6) rotate(-30)" />
<path id="transition-path" d="M141.24,256.6 t100,100 t150,-150 t75,100 T656.87,524.9" stroke="#a80" fill="none" />
<!--path id="transition-path" d="M250,200 t100,100 t50,-100 t100,100 T500,450" stroke="#a80" fill="none" /-->
</g>
<script type="application/javascript"><![CDATA[
// ISSUES:
// - unpredictable behavior when path ends have same X (resp. Y) and frame centers have different X (resp. Y)
// - the transition path must not be transformed, except for the global transformation on container
var svgns = "http://www.w3.org/2000/svg";
var container = document.getElementById("container");
function getRectangleGeometry (id) {
// Get the coordinates and dimensions of the rectangle
var rect = document.getElementById(id);
var x = parseFloat(rect.getAttribute("x"));
var y = parseFloat(rect.getAttribute("y"));
var width = parseFloat(rect.getAttribute("width"));
var height = parseFloat(rect.getAttribute("height"));
// Get the transformation matrix applied to the rectangle
// and compute the scale factor
var matrix = rect.getCTM();
var scale = Math.sqrt(matrix.a * matrix.a + matrix.b * matrix.b);
// Compute the coordinates of the center of the rectangle
var center = document.documentElement.createSVGPoint();
center.x = x + width / 2;
center.y = y + height / 2;
center = center.matrixTransform(matrix);
return {
cx: center.x,
cy: center.y,
width: width * scale,
height: height * scale,
rotate: Math.atan2(matrix.b, matrix.a) * 180 / Math.PI
};
}
var sourceFrame = getRectangleGeometry("source-frame");
var targetFrame = getRectangleGeometry("target-frame");
function getPathGeometry (id, source, target) {
var result = {
path: document.getElementById("transition-path")
};
result.length = result.path.getTotalLength();
var startPoint = result.path.getPointAtLength(0);
var endPoint = result.path.getPointAtLength(result.length);
result.startX = startPoint.x;
result.startY = startPoint.y;
result.scaleX = (target.cx - source.cx) / ((endPoint.x - startPoint.x) || 1);
result.scaleY = (target.cy - source.cy) / ((endPoint.y - startPoint.y) || 1);
return result;
}
var sourceToTargetPath = getPathGeometry("transition-path", sourceFrame, targetFrame);
var startDateMs;
var endDateMs;
var horloge;
function start(dureeMs, intervalleMs) {
startDateMs = Date.now();
endDateMs = startDateMs + dureeMs;
horloge = window.setInterval(step, intervalleMs);
}
function step () {
var dateMs = Date.now();
if(dateMs >= endDateMs) {
window.clearInterval(horloge);
dateMs = endDateMs;
}
var progress = (dateMs - startDateMs) / (endDateMs - startDateMs);
var remaining = 1 - progress;
// Interpolate source and target rectangles
var inter = {};
for (var attr in sourceFrame) {
inter[attr] = sourceFrame[attr] * remaining + targetFrame[attr] * progress;
}
// Interpolate center coordinates to follow the transition path
var transitionPoint = sourceToTargetPath.path.getPointAtLength(sourceToTargetPath.length * progress);
inter.cx = sourceFrame.cx + sourceToTargetPath.scaleX * (transitionPoint.x - sourceToTargetPath.startX);
inter.cy = sourceFrame.cy + sourceToTargetPath.scaleY * (transitionPoint.y - sourceToTargetPath.startY);
// Compute the coordinates and dimensions of the display area
var scale = Math.min(window.innerWidth / inter.width, window.innerHeight / inter.height);
var displayWidth = inter.width * scale;
var displayHeight = inter.height * scale;
var displayX = (window.innerWidth - displayWidth) / 2;
var displayY = (window.innerHeight - displayHeight) / 2;
// Compute the translation to perform on the document
var translateX = - inter.cx + inter.width / 2 + displayX / scale;
var translateY = - inter.cy + inter.height / 2 + displayY / scale;
// Perform the transformation
container.setAttribute("transform",
"scale(" + scale + ") " +
"translate(" + translateX + "," + translateY + ") " +
"rotate(" + (- inter.rotate) + "," + inter.cx + "," + inter.cy + ")"
);
// Add traces of the border and the center of the display area
var m = container.getCTM().inverse();
var transform = "matrix(" + m.a + "," + m.b + "," + m.c + "," + m.d + "," + m.e + "," + m.f + ")";
var rect = document.createElementNS(svgns, "rect");
rect.setAttribute("x", displayX);
rect.setAttribute("y", displayY);
rect.setAttribute("width", displayWidth);
rect.setAttribute("height", displayHeight);
rect.setAttribute("stroke", "blue");
rect.setAttribute("fill", "none");
rect.setAttribute("transform", transform);
container.appendChild(rect);
var circle = document.createElementNS(svgns, "circle");
circle.setAttribute("cx", displayX + displayWidth / 2);
circle.setAttribute("cy", displayY + displayHeight / 2);
circle.setAttribute("r", 10);
circle.setAttribute("stroke", "blue");
circle.setAttribute("fill", "blue");
circle.setAttribute("transform", transform);
container.appendChild(circle);
if(dateMs === endDateMs) {
stop();
}
}
function stop () {
window.setTimeout(function () {
container.removeAttribute("transform");
}, 1000);
}
document.documentElement.onclick = function() {
start(3000, 40);
};
]]></script>
</svg>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment