<svg width="100%"
height="100%"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
id="map-container">
<text x="50%"
y="50%"
fill="silver"
font-size="55"
style="opacity: .5;"
text-anchor="middle">Click area</text>
<svg id="routes-container"></svg>
<svg id="planes-container">
<g class="plane">
<rect x="-24" y="-4"
width="48" height="8"></rect>
<image xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAACHFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAb++m0AAAAAXRSTlMAQObYZgAAAllJREFUeAHt2olS01AYxfHzmIJaioq4IFCx4lIXXERZEKwWpdWCCqXB0uW8oDPO1Cb03tpMwnfGmfxe4X+SXC5FJpPJZDKpKrDV161AoMeBBdjbYAjsTTKkCns/GTIPcw8pLkBxgR2GLcJanuICAcPqsPacEfdgjeICu4z4BmO3GLUEY21xgTek9hmguECN2gIFnvFDcAyKeARTaxQXoLjAd2oL3KW4AMUFNqktcIHiAscccghDyxy2AkMUF9jmsAbsTFFc4ERc4BnFBSgu8IXaAjcYv8DNNA+Nbbq8HTMaEnvN+AWO2PcVidEpwAhl/vUESdUZu8DjNKc6z9gF8qm+LHrxC3Q5sIqE1hm7wD7TLMDYBd4zpJ3eQTzqBD5Fhm3Bb3avefwPjRY9fj2471R8yYgr8KrQArzWaOEjvGhiDj6LFBdYpYU9eL2ihS14XaWFAH498QZQoIUX8CvSwAFGWdo+2B+tfkSP3mnHrcsIJPWJbs0xbzKXBZ/jTvRNkNRTui3A51K6DdCiU23Mq4zbSGqabmP+U6+M89rhHfgdptoAsRsAHMif0+UAOd6fptxEcq14DaJf2zaSm47fALU0R4AKXTBStVb9o15ECugyBzslOnyGoYAOUF9UXldf1e7CkroBSuoGaKob5PwNdDuchSl/A9kOOwC078MpmJrhWTuwVVY3gLoBVtQNEKgbzKgb4IO6AfwNRDu8CPEON2DtGiNa6t/UchLm1A1QUjdAQ/wcIKduEN3hKQQYNgHxDt9BvMMAEO8QEO9wHQr5y325CWQymUzm//AbFsetgyAKUVUAAAAASUVORK5CYII="
x="-12px" y="-12px"
height="24px" width="24px"></image>
</g>
</svg>
</svg>
html,
body {
width: 100%;
height: 100%;
background: #E7DBDB;
margin: 0;
}
#map-container {
cursor: cell;
}
#plane-container {
width: 100%;
height: 100%;
}
.plane rect {
fill: #E7DBDB;
fill-opacity: 1;
}
.plane image {
}
.fly-animation {
fill: none;
stroke: rgb(68, 62, 58);
stroke-width: 2px;
}
/* Example */
window.onclick = function (e) {
var plane = document.getElementsByClassName('plane')[0];
var planesContainer = document.getElementById('planes-container');
var routesContainer = document.getElementById('routes-container');
var mapContainer = document.getElementById('map-container');
var mapContainerRect = mapContainer.getBoundingClientRect();
var fly = new Fly(plane, {
x: mapContainerRect.width / 2,
y: mapContainerRect.height / 2
}, {
x: e.layerX,
y: e.layerY
}, 1);
fly.animate();
};
/**
* Represents a fly
* @constructor
* @param element {HTMLElement}
* @param fromXY {Object}
* @param toXY {Object}
* @param duration {String}
* */
function Fly(element, fromXY, toXY, duration) {
this.element = element;
this.element.setAttribute('visibility', 'hidden');
var elementRect = element.getBBox();
this.from = fromXY;
this.to = toXY;
this.time = duration;
this.elementW = elementRect.width;
this.elementH = elementRect.height;
this.elementX = elementRect.x;
this.elementY = elementRect.y;
/* Break line width */
this.dashLength = this.elementW * 4;
this.container = document.getElementById('routes-container');
this.path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
this.path.setAttribute('class', 'fly-animation');
this.path.style.strokeDasharray = [0, 1].join(' ');
this.setCenterPoint();
this.setPath();
this.containerClear();
this.container.appendChild(this.path);
}
Fly.prototype.containerClear = function () {
while (this.container.firstChild) {
this.container.removeChild(this.container.firstChild);
}
};
Fly.prototype.setPath = function () {
var d = [
'M', this.from.x, ',', this.from.y,
'C', this.cp1.x, ',', this.cp1.y, this.cp2.x, ',', this.cp2.y, this.to.x, ',', this.to.y
].join(' ');
this.path.setAttribute('d', d);
};
Fly.prototype.setCenterPoint = function () {
var _x = 0, _y = 0;
this.cp1 = {};
this.cp2 = {};
if (this.to.x > this.from.x && this.from.y > this.to.y) {
/* start 1 part */
this.cp1.x = this.from.x;
this.cp1.y = this.from.y - ((this.from.y - this.to.y) * 0.7);
this.cp2.x = this.from.x + ((this.to.x - this.from.x) * 0.7);
this.cp2.y = this.to.y;
_x = this.to.x - this.from.x;
_y = this.from.y - this.to.y;
if (_x <= 120)
this.cp1.x = this.from.x * 0.77;
if (_y <= 120)
this.cp2.y = this.to.y * 0.77;
if (_x <= 70 && _y <= 70) {
this.cp1.x = this.from.x;
this.cp2.y = this.to.y;
}
/* end 1 part */
} else if (this.to.x > this.from.x && this.to.y > this.from.y) {
/* start 2 part */
this.cp1.x = this.from.x + ((this.to.x - this.from.x) * 0.7);
this.cp1.y = this.from.y;
this.cp2.x = this.to.x;
this.cp2.y = this.to.y - ((this.to.y - this.from.y) * 0.7);
_x = this.to.x - this.from.x;
_y = this.to.y - this.from.y;
if (_x <= 120)
this.cp2.x = this.to.x * 1.2;
if (_y <= 120)
this.cp1.y = this.from.y * 0.77;
if (_x <= 70 && _y <= 70) {
this.cp1.y = this.from.y;
this.cp2.x = this.to.x;
}
/* end 2 part */
} else if (this.from.x > this.to.x && this.from.y > this.to.y) {
/* start 2 part */
this.cp1.x = this.from.x;
this.cp1.y = this.from.y - ((this.from.y - this.to.y) * 0.7);
this.cp2.x = this.to.x + ((this.from.x - this.to.x) * 0.7);
this.cp2.y = this.to.y;
_x = this.from.x - this.to.x;
_y = this.to.y - this.from.y;
if (_x <= 120)
this.cp1.x = this.from.x * 1.2;
if (_y <= 120)
this.cp2.y = this.to.y * 0.77;
if (_x <= 70 && _y <= 70) {
this.cp1.y = this.from.y;
this.cp2.y = this.to.y;
}
/* end 3 part */
} else if (this.to.x < this.from.x && this.to.y > this.from.y) {
/* start 4 part */
this.cp1.x = this.from.x + ((this.to.x - this.from.x) * 0.7);
this.cp1.y = this.from.y;
this.cp2.x = this.to.x;
this.cp2.y = this.to.y - ((this.to.y - this.from.y) * 0.7);
_x = this.from.x - this.to.x;
_y = this.to.y - this.from.y;
if (_x <= 120)
this.cp1.x = this.from.x * 1.2;
if (_y <= 120)
this.cp2.y = this.to.y * 0.77;
if (_x <= 70 && _y <= 70) {
this.cp1.y = this.from.y;
this.cp2.y = this.to.y;
}
/* end 4 part */
} else {
this.cp1.x = this.from.x;
this.cp1.y = this.from.y;
}
};
Fly.prototype.animate = function () {
var self = this;
this.lineData = {};
this.lineData.counter = 0;
this.lineData.length = this.path.getTotalLength();
this.lineDashData = {};
this.lineDashData.counter = 0;
this.lineDashData.counter0 = 0;
this.lineDashData.length = (this.lineData.length / 2) + (this.dashLength / 2);
TweenMax.to(this.lineData, this.time, {
counter: this.lineData.length,
onUpdate: drawLine,
ease: Quart.easeIn,
onComplete: function () {
TweenMax.to(self.lineDashData, self.time / 1.5, {
counter: self.lineDashData.length,
onUpdate: drawDash,
ease: ElasticOut.ease,
onStart: function () {
self.element.setAttribute('visibility', 'visible')
}
});
}
});
function drawLine() {
self.path.style.strokeDasharray = [self.lineData.counter, self.lineData.length].join(' ');
}
function drawDash() {
if (isNaN(self.dashLength)) {
return;
}
self.path.style.strokeDasharray = [self.lineData.counter, self.dashLength].join(' ');
self.path.style.strokeDashoffset = -1 * self.lineDashData.counter + 'px';
var p0 = self.path.getPointAtLength(self.lineDashData.counter0 - (self.dashLength / 2)),
p1 = self.path.getPointAtLength(self.lineDashData.counter - (self.dashLength / 2)),
angle = Math.atan2(p1.y - p0.y, p1.x - p0.x) * 180 / Math.PI;
self.lineDashData.counter0 = self.lineDashData.counter;
self.element.style.transform =
'translate(' + (p1.x - (self.elementH) - self.elementY) + 'px, ' + (p1.y - (self.elementW / 2 + self.elementX)) + 'px) ' +
'rotate(' + (angle + 180) + 'deg' + ')';
}
};
This Pen doesn't use any external CSS resources.