controling svg clock with setInterval - javascript

I made an svg clock :
<svg baseProfile="basic" viewBox="0 0 200 200" preserveAspectRatio="xMidYMin" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="aiguilles">
<g id="heures" visibility="hidden">
<path fill="#555" d="M94.9,38.8v11l-6,8.2l6.3,9.3L95,92.9c-2.2,1.6-3.7,4.2-3.7,7.1s1.4,5.5,3.6,7l-0.1,12.7l5.3,3.5v-14.5 c0-4.6,0-13.1,0-17.5V33.9L94.9,38.8z"/>
<path fill="#bbb" d="M105,92.9l-0.2-25.6l6.3-9.3l-6-8.2v-11l-5.1-5v57.4c0,5.7,0,12,0,17.5v14.5l5.3-3.5l-0.1-12.7 c2.2-1.6,3.6-4.1,3.6-7C108.7,97.1,107.3,94.5,105,92.9z"/>
<polygon fill="#fff" points="100,69.7 92.6,58.1 100,47.5 107.4,58.1 "/>
</g>
<g id="minutes" visibility="hidden">
<path fill="#777" d="M95.6,9.7v84.9c-1.6,1.3-2.5,3.2-2.5,5.4s1,4.1,2.5,5.4v14.8l4.4,2.6V5.3L95.6,9.7z"/>
<path fill="#ddd" d="M106.9,100c0-2.1-1-4.1-2.5-5.3v-85L100,5.3V93l0,0c0,3,0,10.4,0,13.9l0,0v15.9l4.4-2.6v-14.9 C105.9,104.1,106.9,102.2,106.9,100z"/>
<polygon fill="#fff" points="97.7,17.5 97.7,69.4 100,71.9 102.3,69.4 102.3,17.5 100,15.4 "/>
</g>
<g id="secondes" visibility="hidden">
<path fill="#bbb" d="M101,95.5c0-33.1,0-54.1,0-67.4c2.5-0.5,4.4-2.7,4.4-5.3s-1.9-4.8-4.4-5.3c0-12.4,0-12.7,0-13.3 c0-0.8-0.5-1.8-1-2c-0.6,0.3-1,1.2-1,2c0,0.6,0,0.9,0,13.3c-2.5,0.5-4.4,2.7-4.4,5.3s1.9,4.8,4.4,5.3c0,13.3,0,34.3,0,67.4 c-2.2,0.5-3.8,2.4-3.8,4.7c0,1.2,0.5,2.3,1.2,3.1V122l3.7,3.3l3.7-3.3v-18.7c0.7-0.8,1.2-1.9,1.2-3.1 C104.8,97.9,103.2,95.9,101,95.5z"/>
<circle fill="#FFF" cx="100" cy="22.7" r="3.7"/>
</g>
</g>
</svg>
and I suceeded in animating the hands so that they first go from 12:00 to the current time with the "toTimeNow" function and then show the time continuously with the "timeNow" function :
var tailleAiguilles = 2;
var aiguillesGauche = 250;
var aiguillesHaut = 250;
var aiguilles = document.getElementById("aiguilles");
function transformSvg() {
aiguilles.setAttribute("transform", "matrix(" + tailleAiguilles + " 0 0 " + tailleAiguilles + " " + aiguillesGauche + " " + aiguillesHaut + ")");
}
function toTimeNow() {
var hr = 0;
var mn = 0;
var sc = 0;
var interval = setInterval(function() {
var maintenant = new Date();
var heures = maintenant.getHours();
var minutes = maintenant.getMinutes();
var secondes = maintenant.getSeconds();
var milliSecondes = maintenant.getMilliseconds();
var secondes2 = 6 * secondes;
if (heures * 30 + minutes * 0.5 > 359) {
var angleHeures = heures * 30 + minutes * 0.5 - 359;
}
else {
var angleHeures = heures * 30 + minutes * 0.5;
}
var angleMinutes = minutes * 6 + (0.1 * secondes);
var angleSecondes = secondes2 += 0.006 * milliSecondes;
var max = Math.max(angleHeures, angleMinutes, angleSecondes)
if (hr < angleHeures) {
hr += angleHeures / max * 2;
rotation('heures', hr, 100);
}
if (mn < angleMinutes) {
mn += angleMinutes / max * 2;
rotation('minutes', mn, 100);
}
if (sc < angleSecondes) {
sc += angleSecondes / max * 2;
rotation('secondes', sc, 100);
}
else {
clearInterval(interval);
timeNow();
}
}, 5);
}
function timeNow() {
setInterval(function() {
var maintenant = new Date();
var heures = maintenant.getHours();
var minutes = maintenant.getMinutes();
var secondes = maintenant.getSeconds();
var milliSecondes = maintenant.getMilliseconds();
var secondes2 = 6 * secondes;
rotation('heures', heures * 30 + minutes * 0.5, 100);
rotation('minutes', minutes * 6 + (0.1 * secondes), 100);
rotation('secondes', secondes2 += 0.006 * milliSecondes, 100);
}, 50);
}
function rotation(id, angle, centre) {
var element = document.getElementById(id);
if (element) {
element.setAttribute('transform', 'rotate(' + angle + ', ' + centre + ', ' + centre + ')');
if (element.getAttribute('visibility') == 'hidden') {
element.setAttribute('visibility', 'visible');
}
}
}
window.addEventListener('load', toTimeNow, false);
But I noticed that just before the 2nd function takes over the hands move slightly backwards. If somebody could tell me why I'd appreciate it.

It is almost certainly because of this code:
if (mn < angleMinutes) {
mn += angleMinutes / max * 2;
rotation('minutes', mn, 100);
}
If mn is slightly less than angleMinutes, then this code can result in it stepping well past it. You should check mn after you increment it.
if (mn < angleMinutes) {
mn = Math.min(angleMinutes, mn + angleMinutes / max * 2);
rotation('minutes', mn, 100);
}

Related

JS radio player keeps resetting while navigating the website

I am using a JS radio player on a website. Anytime someone goes to a new page or refreshes the current page the volume slider resets to 25%. Also, Every time someone goes to a new page the radio player restarts itself causing a break in the music. What would be the best way to fix these issues so that it remembers the users volume and the music doesn't break upon switching pages? I currently have the initial volume set to 25% on load up because it is extremely loud for some reason.
--EDIT--
My question about volume has been answered, now I just need a solution for keeping the player from restarting while switching pages.
Radio Player JS:
'use strict';
var audioPlayer = document.querySelector('.ggr-radio-player');
var playPause = audioPlayer.querySelector('#playPause');
var playpauseBtn = audioPlayer.querySelector('.play-pause-btn');
var loading = audioPlayer.querySelector('.loading');
var progress = audioPlayer.querySelector('.ggr-progress');
var sliders = audioPlayer.querySelectorAll('.ggr-slider');
var volumeBtn = audioPlayer.querySelector('.ggr-volume-btn');
var volumeControls = audioPlayer.querySelector('.ggr-volume-controls');
var volumeProgress = volumeControls.querySelector('.ggr-slider .ggr-progress');
var player = audioPlayer.querySelector('audio');
var currentTime = audioPlayer.querySelector('.current-time');
var totalTime = audioPlayer.querySelector('.total-time');
var speaker = audioPlayer.querySelector('#speaker');
var draggableClasses = ['pin'];
var currentlyDragged = null;
player.volume = 0.25;
window.addEventListener('mousedown', function (event) {
if (!isDraggable(event.target)) return false;
currentlyDragged = event.target;
var handleMethod = currentlyDragged.dataset.method;
this.addEventListener('mousemove', window[handleMethod], false);
window.addEventListener('mouseup', function () {
currentlyDragged = false;
window.removeEventListener('mousemove', window[handleMethod], false);
}, false);
});
playpauseBtn.addEventListener('click', togglePlay);
player.addEventListener('timeupdate', updateProgress);
player.addEventListener('volumechange', updateVolume);
player.addEventListener('loadedmetadata', function () {
totalTime.textContent = formatTime(player.duration);
});
player.addEventListener('canplay', makePlay);
player.addEventListener('ended', function () {
playPause.attributes.d.value = "M18 12L0 24V0";
player.currentTime = 0;
});
volumeBtn.addEventListener('click', function () {
volumeBtn.classList.toggle('open');
volumeControls.classList.toggle('hidden');
});
window.addEventListener('resize', directionAware);
sliders.forEach(function (slider) {
var pin = slider.querySelector('.pin');
slider.addEventListener('click', window[pin.dataset.method]);
});
directionAware();
function isDraggable(el) {
var canDrag = false;
var classes = Array.from(el.classList);
draggableClasses.forEach(function (draggable) {
if (classes.indexOf(draggable) !== -1) canDrag = true;
});
return canDrag;
}
function inRange(event) {
var rangeBox = getRangeBox(event);
var rect = rangeBox.getBoundingClientRect();
var direction = rangeBox.dataset.direction;
if (direction == 'horizontal') {
var min = rangeBox.offsetLeft;
var max = min + rangeBox.offsetWidth;
if (event.clientX < min || event.clientX > max) return false;
}
else {
var min = rect.top;
var max = min + rangeBox.offsetHeight;
if (event.clientY < min || event.clientY > max) return false;
}
return true;
}
function updateProgress() {
var current = player.currentTime;
var percent = current / player.duration * 100;
progress.style.width = percent + '%';
currentTime.textContent = formatTime(current);
}
function updateVolume() {
volumeProgress.style.height = player.volume * 100 + '%';
if (player.volume >= 0.5) {
speaker.attributes.d.value = 'M14.667 0v2.747c3.853 1.146 6.666 4.72 6.666 8.946 0 4.227-2.813 7.787-6.666 8.934v2.76C20 22.173 24 17.4 24 11.693 24 5.987 20 1.213 14.667 0zM18 11.693c0-2.36-1.333-4.386-3.333-5.373v10.707c2-.947 3.333-2.987 3.333-5.334zm-18-4v8h5.333L12 22.36V1.027L5.333 7.693H0z';
}
else if (player.volume < 0.5 && player.volume > 0.05) {
speaker.attributes.d.value = 'M0 7.667v8h5.333L12 22.333V1L5.333 7.667M17.333 11.373C17.333 9.013 16 6.987 14 6v10.707c2-.947 3.333-2.987 3.333-5.334z';
}
else if (player.volume <= 0.05) {
speaker.attributes.d.value = 'M0 7.667v8h5.333L12 22.333V1L5.333 7.667';
}
}
function getRangeBox(event) {
var rangeBox = event.target;
var el = currentlyDragged;
if (event.type == 'click' && isDraggable(event.target)) {
rangeBox = event.target.parentElement.parentElement;
}
if (event.type == 'mousemove') {
rangeBox = el.parentElement.parentElement;
}
return rangeBox;
}
function getCoefficient(event) {
var slider = getRangeBox(event);
var rect = slider.getBoundingClientRect();
var K = 0;
if (slider.dataset.direction == 'horizontal') {
var offsetX = event.clientX - slider.offsetLeft;
var width = slider.clientWidth;
K = offsetX / width;
}
else if (slider.dataset.direction == 'vertical') {
var height = slider.clientHeight;
var offsetY = event.clientY - rect.top;
K = 1 - offsetY / height;
}
return K;
}
function changeVolume(event) {
if (inRange(event)) {
player.volume = getCoefficient(event);
}
}
function formatTime(time) {
var min = Math.floor(time / 60);
var sec = Math.floor(time % 60);
return min + ':' + (sec < 10 ? '0' + sec : sec);
}
function togglePlay() {
if (player.paused) {
playPause.attributes.d.value = "M0 0h6v24H0zM12 0h6v24h-6z";
player.play();
}
else {
playPause.attributes.d.value = "M18 12L0 24V0";
player.pause();
}
}
function makePlay() {
playpauseBtn.style.display = 'block';
loading.style.display = 'none';
}
function directionAware() {
if (window.innerHeight < 250) {
volumeControls.style.bottom = '-54px';
volumeControls.style.left = '54px';
}
else if (audioPlayer.offsetTop < 154) {
volumeControls.style.bottom = '-164px';
volumeControls.style.left = '-3px';
}
else {
volumeControls.style.bottom = '52px';
volumeControls.style.left = '-3px';
}
}
Radio Player HTML:
<div class="ggr-radio">
<div class="ggr-now-playing">
<marquee behavior="scroll" direction="left" scrollamount="2">
<span id="cc_strinfo_trackartist_gamersguildradio" class="cc_streaminfo"></span> - <span id="cc_strinfo_tracktitle_gamersguildradio" class="cc_streaminfo"></span>
</marquee>
</div>
<div class="audio ggr-radio-player">
<div class="loading">
<div class="spinner"></div>
</div>
<div class="play-pause-btn">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="24" viewBox="0 0 18 24">
<path fill="#566574" fill-rule="evenodd" d="M0 0h6v24H0zM12 0h6v24h-6z" class="play-pause-icon" id="playPause" />
</svg>
</div>
<div class="controls">
<span class="current-time">0:00</span>
</div>
<div class="ggr-volume">
<div class="ggr-volume-btn">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path fill="#566574" fill-rule="evenodd" d="M14.667 0v2.747c3.853 1.146 6.666 4.72 6.666 8.946 0 4.227-2.813 7.787-6.666 8.934v2.76C20 22.173 24 17.4 24 11.693 24 5.987 20 1.213 14.667 0zM18 11.693c0-2.36-1.333-4.386-3.333-5.373v10.707c2-.947 3.333-2.987 3.333-5.334zm-18-4v8h5.333L12 22.36V1.027L5.333 7.693H0z" id="speaker"/>
</svg>
</div>
<div class="ggr-volume-controls hidden">
<div class="ggr-slider" data-direction="vertical">
<div class="ggr-progress">
<div class="pin" id="ggr-volume-pin" data-method="changeVolume">
</div>
</div>
</div>
</div>
</div>
<audio crossorigin autoplay id="radio">
<source src="http://192.95.18.39:5272/stream" type="audio/mp3">
</audio>
</div>
</div>
When the page is loading you setting the slider value everytime: player.volume = 0.25;
When the user changes the slider value or leave the current page, store the value it in a cookie.
When the page is refreshed, load the stored values from your cookie to the player.

Pausing JS Typewriter

I'm using a script on Codepen that mimics a type effect. http://codepen.io/hi-im-si/pen/DHoup.
Trying to create a simple start/stop button. I've added the pause svg button and class, but not quite sure how to get it to pause.
Thanks for any assistance!
Here's the script:
var TxtType = function(el, toRotate, period) {
this.toRotate = toRotate;
this.el = el;
this.loopNum = 0;
this.period = parseInt(period, 10) || 2000;
this.txt = '';
this.tick();
this.isDeleting = false;
};
TxtType.prototype.tick = function() {
var i = this.loopNum % this.toRotate.length;
var fullTxt = this.toRotate[i];
if (this.isDeleting) {
this.txt = fullTxt.substring(0, this.txt.length - 1);
} else {
this.txt = fullTxt.substring(0, this.txt.length + 1);
}
this.el.innerHTML = '<span class="wrap">'+this.txt+'</span>';
var that = this;
var delta = 200 - Math.random() * 100;
if (this.isDeleting) { delta /= 2; }
if (!this.isDeleting && this.txt === fullTxt) {
delta = this.period;
this.isDeleting = true;
} else if (this.isDeleting && this.txt === '') {
this.isDeleting = false;
this.loopNum++;
delta = 500;
}
setTimeout(function() {
that.tick();
}, delta);
};
window.onload = function() {
var elements = document.getElementsByClassName('typewrite');
for (var i=0; i<elements.length; i++) {
var toRotate = elements[i].getAttribute('data-type');
var period = elements[i].getAttribute('data-period');
if (toRotate) {
new TxtType(elements[i], JSON.parse(toRotate), period);
}
}
// INJECT CSS
var css = document.createElement("style");
css.type = "text/css";
css.innerHTML = ".typewrite > .wrap { border-right: 0.08em solid #fff}";
document.body.appendChild(css);
};
body {
background-color:#ce3635;
text-align: center;
color:#fff;
padding-top:10em;
font-family:Helvetica;
}
* { color:#fff; text-decoration: none;}
<div class="type-wrap">
<h2>
<a href="" class="typewrite" data-period="2000" data-type='[ "Hi, My name is Justin.", "I am Creative.", "I Love Design.", "I Love to Develop." ]'>
<span class="wrap"></span></a>
</h2>
</div>
<div class="controls">
<a href="#" class="stop-start-btn"><span class="icon-pause"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="30px" height="30px" viewBox="0 0 24 24"><g transform="translate(0, 0)">
<line data-color="color-2" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="square" stroke-miterlimit="10" x1="9" y1="16" x2="9" y2="8" stroke-linejoin="miter"/>
<line data-color="color-2" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="square" stroke-miterlimit="10" x1="15" y1="16" x2="15" y2="8" stroke-linejoin="miter"/>
<circle fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="square" stroke-miterlimit="10" cx="12" cy="12" r="11" stroke-linejoin="miter"/>
</g></svg></span></a>
</div>
var TxtType = function(el, toRotate, period) {
this.toRotate = toRotate;
this.el = el;
this.loopNum = 0;
this.period = parseInt(period, 10) || 2000;
this.txt = '';
this.tick();
this.lastDeletingStatus=0;
this.isDeleting = 0;
};
var timer;
TxtType.prototype.tick = function() {
var i = this.loopNum % this.toRotate.length;
var fullTxt = this.toRotate[i];
if (this.isDeleting===1) {
this.txt = fullTxt.substring(0, this.txt.length - 1);
} else {
this.txt = fullTxt.substring(0, this.txt.length + 1);
}
this.el.innerHTML = '<span class="wrap">'+this.txt+'</span>';
var that = this;
var delta = 200 - Math.random() * 100;
if (this.isDeleting===1) { delta /= 2; }
if (this.isDeleting===0 && this.txt === fullTxt) {
delta = this.period;
this.isDeleting = 1;
} else if (this.isDeleting===1 && this.txt === '') {
this.isDeleting = 0;
this.loopNum++;
delta = 500;
}
if(this.isDeleting!==2){
timer=setTimeout(function() {
that.tick();
}, delta);
}
};
TxtType.prototype.toggleStart=function(){
//start back up
if(this.isDeleting===2){
this.isDeleting=this.lastDeletingStatus;
this.lastDeletingStatus=2;
}
//stop
else{
this.lastDeletingStatus=this.isDeleting;
this.isDeleting=2;
clearTimeout(timer);
}
}
var toggleStart=function(){
txtType.toggleStart();
txtType.tick();
}
var txtType;
window.onload = function() {
var elements = document.getElementsByClassName('typewrite');
for (var i=0; i<elements.length; i++) {
var toRotate = elements[i].getAttribute('data-type');
var period = elements[i].getAttribute('data-period');
if (toRotate) {
txtType=new TxtType(elements[i], JSON.parse(toRotate), period);
}
}
// INJECT CSS
var css = document.createElement("style");
css.type = "text/css";
css.innerHTML = ".typewrite > .wrap { border-right: 0.08em solid #fff}";
document.body.appendChild(css);
};
body {
background-color:#ce3635;
text-align: center;
color:#fff;
padding-top:10em;
font-family:Helvetica;
}
* { color:#fff; text-decoration: none;}
<div class="type-wrap">
<h2>
<a href="" class="typewrite" data-period="2000" data-type='[ "Hi, My name is Justin.", "I am Creative.", "I Love Design.", "I Love to Develop." ]'>
<span class="wrap"></span></a>
</h2>
</div>
<div class="controls">
<a href="#" class="stop-start-btn"><span class="icon-pause" ><svg onclick="toggleStart()" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="30px" height="30px" viewBox="0 0 24 24"><g transform="translate(0, 0)">
<line data-color="color-2" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="square" stroke-miterlimit="10" x1="9" y1="16" x2="9" y2="8" stroke-linejoin="miter"/>
<line data-color="color-2" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="square" stroke-miterlimit="10" x1="15" y1="16" x2="15" y2="8" stroke-linejoin="miter"/>
<circle fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="square" stroke-miterlimit="10" cx="12" cy="12" r="11" stroke-linejoin="miter"/>
</g></svg></span></a>
</div>
isDeleting shouldn't be a boolean. It should be able to hold three values. isDeleting=0, isDeleting=1, isDeleting=2 (which is the stopped state).
Then create a function TxtType.prototype.toggleStart that sets this.isDeleting to 2 if it isn't 2, and sets it to the previous value of this.isDeleting if it is two.
To implement this, do the following:
1) Create a global variable, called txtType. In window.onload, set it equal to new TxtType(...). This way, you can access the object from other functions. It would look something like this:
var txtType;
window.onload = function() {
var elements = document.getElementsByClassName('typewrite');
for (var i=0; i<elements.length; i++) {
var toRotate = elements[i].getAttribute('data-type');
var period = elements[i].getAttribute('data-period');
if (toRotate) {
txtType=new TxtType(elements[i], JSON.parse(toRotate), period);
}
}
...
};
2)Create a global timer variable that you set equal to the timeout call in tick(). That way, you can clear the timer from other functions. That would look something like this:
var timer;
TxtType.prototype.tick = function() {
......
timer=setTimeout(function() {
that.tick();
}, delta);
}
3)Wherever isDeleting=false, set isDeleting=0. Whenever isDeleting=true, set isDeleting=1. Put an if statement around setTimeout() so that it only runs if isDeleting!==2 (ie. it is not in the stopped state. If it's in the stopped state we do not want this timer to run).
4)Create a function on the prototype of TxtType called toggleStart as follows:
TxtType.prototype.toggleStart=function(){
//start back up
if(this.isDeleting===2){
this.isDeleting=this.lastDeletingStatus;
this.lastDeletingStatus=2;
}
//stop
else{
this.lastDeletingStatus=this.isDeleting;
this.isDeleting=2;
clearTimeout(timer);
}
}
(Initialize this.lastDeletingStatus to 0 in the constructor of TxtType)
5)Create a global function called toggleStart that you can call from html as follows:
var toggleStart=function(){
txtType.toggleStart();
txtType.tick();
}
6)Last step, add onclick="toggleStart()" from the pause svg in your html like this:
<svg onclick="toggleStart()" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="30px" height="30px" viewBox="0 0 24 24">
Tada!!!!

SVG smooth freehand drawing

I implemented a freehand drawing of a path using native JS. But as expected path edges are little aggressive and not smooth. So I have an option of using simplifyJS to simplify points and then redraw path. But like here, instead of smoothening after drawing, I am trying to find simplified edges while drawing
Here is my code:
var x0, y0;
var dragstart = function(event) {
var that = this;
var pos = coordinates(event);
x0 = pos.x;
y0 = pos.y;
that.points = [];
};
var dragging = function(event) {
var that = this;
var xy = coordinates(event);
var points = that.points;
var x1 = xy.x, y1 = xy.y, dx = x1 - x0, dy = y1 - y0;
if (dx * dx + dy * dy > 100) {
xy = {
x: x0 = x1,
y: y0 = y1
};
} else {
xy = {
x: x1,
y: y1
};
}
points.push(xy);
};
But it is not working as in the link added above. Still edges are not good. Please help.
The following code snippet makes the curve smoother by calculating the average of the last mouse positions. The level of smoothing depends on the size of the buffer in which these values are kept. You can experiment with the different buffer sizes offered in the dropdown list. The behavior with a 12 point buffer is somewhat similar to the Mike Bostock's code snippet that you refer to in the question.
More sophisticated techniques could be implemented to get the smoothed point from the positions stored in the buffer (weighted average, linear regression, cubic spline smoothing, etc.) but this simple average method may be sufficiently accurate for your needs.
var strokeWidth = 2;
var bufferSize;
var svgElement = document.getElementById("svgElement");
var rect = svgElement.getBoundingClientRect();
var path = null;
var strPath;
var buffer = []; // Contains the last positions of the mouse cursor
svgElement.addEventListener("mousedown", function (e) {
bufferSize = document.getElementById("cmbBufferSize").value;
path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute("fill", "none");
path.setAttribute("stroke", "#000");
path.setAttribute("stroke-width", strokeWidth);
buffer = [];
var pt = getMousePosition(e);
appendToBuffer(pt);
strPath = "M" + pt.x + " " + pt.y;
path.setAttribute("d", strPath);
svgElement.appendChild(path);
});
svgElement.addEventListener("mousemove", function (e) {
if (path) {
appendToBuffer(getMousePosition(e));
updateSvgPath();
}
});
svgElement.addEventListener("mouseup", function () {
if (path) {
path = null;
}
});
var getMousePosition = function (e) {
return {
x: e.pageX - rect.left,
y: e.pageY - rect.top
}
};
var appendToBuffer = function (pt) {
buffer.push(pt);
while (buffer.length > bufferSize) {
buffer.shift();
}
};
// Calculate the average point, starting at offset in the buffer
var getAveragePoint = function (offset) {
var len = buffer.length;
if (len % 2 === 1 || len >= bufferSize) {
var totalX = 0;
var totalY = 0;
var pt, i;
var count = 0;
for (i = offset; i < len; i++) {
count++;
pt = buffer[i];
totalX += pt.x;
totalY += pt.y;
}
return {
x: totalX / count,
y: totalY / count
}
}
return null;
};
var updateSvgPath = function () {
var pt = getAveragePoint(0);
if (pt) {
// Get the smoothed part of the path that will not change
strPath += " L" + pt.x + " " + pt.y;
// Get the last part of the path (close to the current mouse position)
// This part will change if the mouse moves again
var tmpPath = "";
for (var offset = 2; offset < buffer.length; offset += 2) {
pt = getAveragePoint(offset);
tmpPath += " L" + pt.x + " " + pt.y;
}
// Set the complete current path coordinates
path.setAttribute("d", strPath + tmpPath);
}
};
html, body
{
padding: 0px;
margin: 0px;
}
#svgElement
{
border: 1px solid;
margin-top: 4px;
margin-left: 4px;
cursor: default;
}
#divSmoothingFactor
{
position: absolute;
left: 14px;
top: 12px;
}
<div id="divSmoothingFactor">
<label for="cmbBufferSize">Buffer size:</label>
<select id="cmbBufferSize">
<option value="1">1 - No smoothing</option>
<option value="4">4 - Sharp curves</option>
<option value="8" selected="selected">8 - Smooth curves</option>
<option value="12">12 - Very smooth curves</option>
<option value="16">16 - Super smooth curves</option>
<option value="20">20 - Hyper smooth curves</option>
</select>
</div>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="svgElement" x="0px" y="0px" width="600px" height="400px" viewBox="0 0 600 400" enable-background="new 0 0 600 400" xml:space="preserve">
Quadtratic Bézier polyline smoothing
#ConnorsFan solution works great and is probably providing a better rendering performance and more responsive drawing experience.
In case you need a more compact svg output (in terms of markup size) quadratic smoothing might be interesting.
E.g. if you need to export the drawings in an efficient way.
Simplified example: polyline smoothing
Green dots show the original polyline coordinates (in x/y pairs).
Purple points represent interpolated middle coordinates – simply calculated like so:
[(x1+x2)/2, (y1+y2)/2].
The original coordinates (highlighted green) become quadratic bézier control points
whereas the interpolated middle points will be the end points.
let points = [{
x: 0,
y: 10
},
{
x: 10,
y: 20
},
{
x: 20,
y: 10
},
{
x: 30,
y: 20
},
{
x: 40,
y: 10
}
];
path.setAttribute("d", smoothQuadratic(points));
function smoothQuadratic(points) {
// set M/starting point
let [Mx, My] = [points[0].x, points[0].y];
let d = `M ${Mx} ${My}`;
renderPoint(svg, [Mx, My], "green", "1");
// split 1st line segment
let [x1, y1] = [points[1].x, points[1].y];
let [xM, yM] = [(Mx + x1) / 2, (My + y1) / 2];
d += `L ${xM} ${yM}`;
renderPoint(svg, [xM, yM], "purple", "1");
for (let i = 1; i < points.length; i += 1) {
let [x, y] = [points[i].x, points[i].y];
// calculate mid point between current and next coordinate
let [xN, yN] = points[i + 1] ? [points[i + 1].x, points[i + 1].y] : [x, y];
let [xM, yM] = [(x + xN) / 2, (y + yN) / 2];
// add quadratic curve:
d += `Q${x} ${y} ${xM} ${yM}`;
renderPoint(svg, [xM, yM], "purple", "1");
renderPoint(svg, [x, y], "green", "1");
}
return d;
}
pathRel.setAttribute("d", smoothQuadraticRelative(points));
function smoothQuadraticRelative(points, skip = 0, decimals = 3) {
let pointsL = points.length;
let even = pointsL - skip - (1 % 2) === 0;
// set M/starting point
let type = "M";
let values = [points[0].x, points[0].y];
let [Mx, My] = values.map((val) => {
return +val.toFixed(decimals);
});
let dRel = `${type}${Mx} ${My}`;
// offsets for relative commands
let xO = Mx;
let yO = My;
// split 1st line segment
let [x1, y1] = [points[1].x, points[1].y];
let [xM, yM] = [(Mx + x1) / 2, (My + y1) / 2];
let [xMR, yMR] = [xM - xO, yM - yO].map((val) => {
return +val.toFixed(decimals);
});
dRel += `l${xMR} ${yMR}`;
xO += xMR;
yO += yMR;
for (let i = 1; i < points.length; i += 1 + skip) {
// control point
let [x, y] = [points[i].x, points[i].y];
let [xR, yR] = [x - xO, y - yO];
// next point
let [xN, yN] = points[i + 1 + skip] ?
[points[i + 1 + skip].x, points[i + 1 + skip].y] :
[points[pointsL - 1].x, points[pointsL - 1].y];
let [xNR, yNR] = [xN - xO, yN - yO];
// mid point
let [xM, yM] = [(x + xN) / 2, (y + yN) / 2];
let [xMR, yMR] = [(xR + xNR) / 2, (yR + yNR) / 2];
type = "q";
values = [xR, yR, xMR, yMR];
// switch to t command
if (i > 1) {
type = "t";
values = [xMR, yMR];
}
dRel += `${type}${values
.map((val) => {
return +val.toFixed(decimals);
})
.join(" ")} `;
xO += xMR;
yO += yMR;
}
// add last line if odd number of segments
if (!even) {
values = [points[pointsL - 1].x - xO, points[pointsL - 1].y - yO];
dRel += `l${values
.map((val) => {
return +val.toFixed(decimals);
})
.join(" ")}`;
}
return dRel;
}
function renderPoint(svg, coords, fill = "red", r = "2") {
let marker =
'<circle cx="' +
coords[0] +
'" cy="' +
coords[1] +
'" r="' +
r +
'" fill="' +
fill +
'" ><title>' +
coords.join(", ") +
"</title></circle>";
svg.insertAdjacentHTML("beforeend", marker);
}
svg {
border: 1px solid #ccc;
width: 45vw;
overflow: visible;
margin-right: 1vw;
}
path {
fill: none;
stroke-linecap: round;
stroke-linejoin: round;
stroke-opacity: 0.5;
}
<svg id="svg" viewBox="0 0 40 30">
<path d="M 0 10 L 10 20 20 10 L 30 20 40 10" fill="none" stroke="#999" stroke-width="1"></path>
<path id="path" d="" fill="none" stroke="red" stroke-width="1" />
</svg>
<svg id="svg2" viewBox="0 0 40 30">
<path d="M 0 10 L 10 20 20 10 L 30 20 40 10" fill="none" stroke="#999" stroke-width="1"></path>
<path id="pathRel" d="" fill="none" stroke="red" stroke-width="1" />
</svg>
Example: Svg draw Pad
const svg = document.getElementById("svg");
const svgns = "http://www.w3.org/2000/svg";
let strokeWidth = 0.25;
// rounding and smoothing
let decimals = 2;
let getNthMouseCoord = 1;
let smooth = 2;
// init
let isDrawing = false;
var points = [];
let path = "";
let pointCount = 0;
const drawStart = (e) => {
pointCount = 0;
isDrawing = true;
// create new path
path = document.createElementNS(svgns, "path");
svg.appendChild(path);
};
const draw = (e) => {
if (isDrawing) {
pointCount++;
if (getNthMouseCoord && pointCount % getNthMouseCoord === 0) {
let point = getMouseOrTouchPos(e);
// save to point array
points.push(point);
}
if (points.length > 1) {
let d = smoothQuadratic(points, smooth, decimals);
path.setAttribute("d", d);
}
}
};
const drawEnd = (e) => {
isDrawing = false;
points = [];
// just illustrating the ouput
svgMarkup.value = svg.outerHTML;
};
// start drawing: create new path;
svg.addEventListener("mousedown", drawStart);
svg.addEventListener("touchstart", drawStart);
svg.addEventListener("mousemove", draw);
svg.addEventListener("touchmove", draw);
// stop drawing, reset point array for next line
svg.addEventListener("mouseup", drawEnd);
svg.addEventListener("touchend", drawEnd);
svg.addEventListener("touchcancel", drawEnd);
function smoothQuadratic(points, skip = 0, decimals = 3) {
let pointsL = points.length;
let even = pointsL - skip - (1 % 2) === 0;
// set M/starting point
let type = "M";
let values = [points[0].x, points[0].y];
let [Mx, My] = values.map((val) => {
return +val.toFixed(decimals);
});
let dRel = `${type}${Mx} ${My}`;
// offsets for relative commands
let xO = Mx;
let yO = My;
// split 1st line segment
let [x1, y1] = [points[1].x, points[1].y];
let [xM, yM] = [(Mx + x1) / 2, (My + y1) / 2];
let [xMR, yMR] = [xM - xO, yM - yO].map((val) => {
return +val.toFixed(decimals);
});
dRel += `l${xMR} ${yMR}`;
xO += xMR;
yO += yMR;
for (let i = 1; i < points.length; i += 1 + skip) {
// control point
let [x, y] = [points[i].x, points[i].y];
let [xR, yR] = [x - xO, y - yO];
// next point
let [xN, yN] = points[i + 1 + skip] ?
[points[i + 1 + skip].x, points[i + 1 + skip].y] :
[points[pointsL - 1].x, points[pointsL - 1].y];
let [xNR, yNR] = [xN - xO, yN - yO];
// mid point
let [xM, yM] = [(x + xN) / 2, (y + yN) / 2];
let [xMR, yMR] = [(xR + xNR) / 2, (yR + yNR) / 2];
type = "q";
values = [xR, yR, xMR, yMR];
// switch to t command
if (i > 1) {
type = "t";
values = [xMR, yMR];
}
dRel += `${type}${values
.map((val) => {
return +val.toFixed(decimals);
})
.join(" ")} `;
xO += xMR;
yO += yMR;
}
// add last line if odd number of segments
if (!even) {
values = [points[pointsL - 1].x - xO, points[pointsL - 1].y - yO];
dRel += `l${values
.map((val) => {
return +val.toFixed(decimals);
})
.join(" ")}`;
}
return dRel;
}
/**
* based on:
* #Daniel Lavedonio de Lima
* https://stackoverflow.com/a/61732450/3355076
*/
function getMouseOrTouchPos(e) {
let x, y;
// touch cooordinates
if (
e.type == "touchstart" ||
e.type == "touchmove" ||
e.type == "touchend" ||
e.type == "touchcancel"
) {
let evt = typeof e.originalEvent === "undefined" ? e : e.originalEvent;
let touch = evt.touches[0] || evt.changedTouches[0];
x = touch.pageX;
y = touch.pageY;
} else if (
e.type == "mousedown" ||
e.type == "mouseup" ||
e.type == "mousemove" ||
e.type == "mouseover" ||
e.type == "mouseout" ||
e.type == "mouseenter" ||
e.type == "mouseleave"
) {
x = e.clientX;
y = e.clientY;
}
// get svg user space coordinates
let point = svg.createSVGPoint();
point.x = x;
point.y = y;
let ctm = svg.getScreenCTM().inverse();
point = point.matrixTransform(ctm);
return point;
}
body {
margin: 0;
font-family: sans-serif;
padding: 1em;
}
* {
box-sizing: border-box;
}
svg {
width: 100%;
max-height: 75vh;
overflow: visible;
}
textarea {
width: 100%;
min-height: 50vh;
resize: none;
}
.border {
border: 1px solid #ccc;
}
path {
fill: none;
stroke: #000;
stroke-linecap: round;
stroke-linejoin: round;
}
input[type="number"] {
width: 3em;
}
input[type="number"]::-webkit-inner-spin-button {
opacity: 1;
}
#media (min-width: 720px) {
svg {
width: 75%;
}
textarea {
width: 25%;
}
.flex {
display: flex;
gap: 1em;
}
.flex * {
flex: 1 0 auto;
}
}
<h2>Draw quadratic bezier (relative commands)</h2>
<p><button type="button" id="clear" onclick="clearDrawing()">Clear</button>
<label>Get nth Mouse position</label><input type="number" id="nthMouseCoord" value="1" min="0" oninput="changeVal()">
<label>Smooth</label><input type="number" id="simplifyDrawing" min="0" value="2" oninput="changeVal()">
</p>
<div class="flex">
<svg class="border" id="svg" viewBox="0 0 200 100">
</svg>
<textarea class="border" id="svgMarkup"></textarea>
</div>
<script>
function changeVal() {
getNthMouseCoord = +nthMouseCoord.value + 1;
simplify = +simplifyDrawing.value;;
}
function clearDrawing() {
let paths = svg.querySelectorAll('path');
paths.forEach(path => {
path.remove();
})
}
</script>
How it works
save mouse/cursor positions in a point array via event listeners
Event Listeners (including touch events):
function getMouseOrTouchPos(e) {
let x, y;
// touch cooordinates
if (e.type == "touchstart" || e.type == "touchmove" || e.type == "touchend" || e.type == "touchcancel"
) {
let evt = typeof e.originalEvent === "undefined" ? e : e.originalEvent;
let touch = evt.touches[0] || evt.changedTouches[0];
x = touch.pageX;
y = touch.pageY;
} else if ( e.type == "mousedown" || e.type == "mouseup" || e.type == "mousemove" || e.type == "mouseover" || e.type == "mouseout" || e.type == "mouseenter" || e.type == "mouseleave") {
x = e.clientX;
y = e.clientY;
}
// get svg user space coordinates
let point = svg.createSVGPoint();
point.x = x;
point.y = y;
let ctm = svg.getScreenCTM().inverse();
point = point.matrixTransform(ctm);
return point;
}
It's crucial to translate HTML DOM cursor coordinates to SVG DOM user units unless your svg viewport corresponds to the HTML placement 1:1.
let ctm = svg.getScreenCTM().inverse();
point = point.matrixTransform(ctm);
optional: skip cursor points and use every nth point respectively (pre processing – aimed at reducing the total amount of cursor coordinates)
optional: similar to the previous measure: smooth by skipping polyine segments – the curve control point calculation will skip succeeding mid and control points (post processing – calculate curves based on retrieved point array but skip points).
Q to T simplification: Since we are splitting the polyline coordinates evenly we can simplify the path d output by using the quadratic shorthand command T repeating the previous tangents.
Converting to relative commands and rounding
Based on x/y offsets globally incremented by the previous command's end point.
Depending on your layout sizes you need to tweak smoothing values.
For a "micro smoothing" you should also include these css properties:
path {
fill: none;
stroke: #000;
stroke-linecap: round;
stroke-linejoin: round;
}
Further reading
Change T command to Q command in SVG
There are already some implementations for this on github e.g. https://github.com/epistemex/cardinal-spline-js
You dont have to change anything on your input for that and can only change the draw function, that the line between the points is smooth. With that the points dont slip a bit during the simplification.

German style railway clock. Appalling use of SVG?

Don't laugh, my knowledge of SVG practically zero. This is my first attempt at animating svg shadows. All I wanted was to dynamically access the filter feOffset's dx and dy attributes to give the clock hands realistic shadow positions as they move around the dial.
The only way I could do it was to rip apart the svg and reassemble it with JavaScript. It works a treat and runs at about 1.5% to 4% cpu on my machine with a setTimeout cycle of about 30mls (needed for smooth second hand). I discarded requestanimationframe because the time goes to pot with long periods without page focus.
As it stands, the script is creating/replacing (I think!) new svg on each new cycle.
Anyway, my question is there a better/proper way to access and manipulate dx and dy?
Ps: I'm only using svg because the shapes I want obviously can't be generated with css and as the clock is fully resizable, an image png etc is unacceptable.
Thanks for any help.
(function () {
/* German Station Style Clock */
/* ^^^^^^^^^^^^ Config below ^^^^^^^^^^^^ */
var clockSize = 500;
var casecol = 'rgba(40,40,40,1.0)';
var dialcol = 'rgba(255,255,255,1.0)';
var numcol = 'rgba(40,40,40,1.0)';
var seccol = 'rgba(200,0,0,1.0)';
var handcol = 'rgba(40,40,40,1.0)';
var shadowOpacity = 0.3;
var shadowBlur = 0.2;
var shadowAngle = 0.6;
var clockShape = 50;
/* (max) 50 = round (min) 0 = square */
var numoutline = 'no';
/* 'yes' or 'no' */
var numfill = 'rgba(255,0,0,1.0)';
var numshad = 'rgba(0,0,0,0.1)';
/* ^^^^^^^^^^^^^ End config ^^^^^^^^^^^^^ */
var d = document;
var dgts = [];
var e = 360/12;
var degr = 0;
var nums = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'];
var tmr;
var mls = 1000 / 30;
var radi = Math.PI / 180;
var offs = 60 * radi;
var canstroke = ('webkitTextStroke' in d.body.style);
var str = '-webkit-text-fill-color: '+numfill+';'
+'-webkit-text-stroke-width: '+xy(0.4)+'px;'
+'-webkit-text-stroke-color: '+numcol+';';
var wks = (canstroke && numoutline == "yes")?str:'';
var broff = (clockShape < 20)?2:0;
var presec;
var premin;
var prehou;
var rnd = 'id'+Math.random() * 1;
var idx = d.getElementsByTagName('div').length;
d.write('<div id = "'+rnd+'" style="display:inline-block;line-height:0px;"></div>');
function xy (a) {
return (a * clockSize / 100);
}
/* Clock dial */
var dial = d.createElement('div');
dial.setAttribute('style', 'display:inline-block;'
+'position: relative;'
+'height: '+clockSize+'px;'
+'width: '+clockSize+'px;'
+'background-color: '+dialcol+';'
+'border: '+xy(2)+'px solid '+casecol+';'
+'border-radius: '+clockShape+'%;');
d.getElementById(rnd).appendChild(dial);
/* Clock markers */
for (var i = 0; i < 12; i++) {
dgts[i] = d.createElement('div');
dgts[i].setAttribute('style', 'display: block;'
+'position: absolute;'
+'width: '+xy(16)+'px;'
+'height: '+xy(14)+'px;'
+'margin: auto;top: 0;bottom: 0; left: 0;right: 0;'
+'font: bold '+xy(13)+'px Arial;'
+'line-height: '+xy(13)+'px;'
+'text-align: center !important;'
+'color: '+numcol+';'+wks+';');
dgts[i].innerHTML = nums[i];
dial.appendChild(dgts[i]);
degr += 30;
dgts[i].style.top = xy(0) + xy(84) * Math.sin(-offs + e * i * radi) + 'px';
dgts[i].style.left= xy(0) + xy(84) * Math.cos(-offs + e * i * radi) + 'px';
dgts[i].style.transform = 'rotate(' + (degr) + 'deg)';
dgts[i].style.transformOrigin = 'center center';
}
/* Generic container div for all hands */
var handContainers = 'display: block;'
+'position: absolute;'
+'height: '+xy(100)+'px;'
+'width: '+xy(20)+'px;'
+'font-size: 0px; line-height: 0px; padding: 0;'
+'margin: auto; top: 0;bottom: 0; left: 0; right: 0;'
+'transform-origin: center center;'
/* Hour hand */
var houHand = d.createElement('div');
houHand.setAttribute('style', handContainers + 'transition: .5s cubic-bezier(0.666, 1.91, 0.333, 0);');
dial.appendChild(houHand);
var houC = d.createElement('div');
var housvg = '<polygon points="94,46 100,40 106,46 106,118 94,118" style="fill:'+handcol+'; stroke:none"/>';
/* Minute hand */
var minHand = d.createElement('div');
minHand.setAttribute('style',handContainers + 'transition: .4s cubic-bezier(0.666, 1.91, 0.333, 0);');
dial.appendChild(minHand);
var minC = d.createElement('div');
var minsvg = '<polygon points="95.5,11.5 100,7 104.5,11.5 104.5,122 95.5,122" style="fill:'+handcol+'; stroke:none"/>';
/* Seconds hand */
var secHand = d.createElement('div');
secHand.setAttribute('style',handContainers);
dial.appendChild(secHand);
var secC = d.createElement('div');
var secsvg = '<polygon points="98.8,11 100,9.8 101.2,11 101.6,42 98.4,42" style="fill:'+seccol+'; stroke:none"/>'+
'<polygon points="98.1,58 101.9,58 102.5,122 97.5,122" style="fill:'+seccol+'; stroke:none"/>'+
'<circle cx="100" cy="50" r="8.5" style="fill:none; stroke:'+seccol+'; stroke-width:6.5"/>';
function dropShadow(s, h) {
var depth = xy(h);
var angle = s * radi - shadowAngle;
var vsa = depth * Math.cos(angle);
var hsa = depth * Math.sin(angle);
return {vsa:vsa, hsa:hsa}
}
var str1 = '<svg height="'+xy(100)+'" width="'+xy(20)+'" viewBox="90.25 -4 20 200" ><defs>';
var str3 = '<feGaussianBlur in="SourceAlpha" stdDeviation="'+shadowBlur+'"/>';
var str5 = '<feFlood flood-color="#000000" flood-opacity="'+shadowOpacity+'"/>'+
'<feComposite in2="offsetblur" operator="in"/>'+
'<feMerge>'+
'<feMergeNode/>'+
'<feMergeNode in="SourceGraphic"/>'+
'</feMerge>'+
'</filter>'+
'</defs>';
var str8 = '</g></svg>';
function dynShad (str2, str4, str6, str7) {
var create = str1 + str2 + str3 + str4 + str5 + str6 + str7 + str8;
return create;
}
function clock() {
var x = new Date();
var time = Math.min(60000, 1.025 * (1000 * x.getSeconds() + x.getMilliseconds()));
var seconds = Math.floor(time / 1000);
var millis = time % 1000;
var germanSec = (6 * seconds + 3 * (1 + Math.cos(Math.PI + Math.PI * (0.001 * millis))));
var minutes = x.getMinutes();
var hours = (x.getHours() * 30) + (minutes / 2);
if (germanSec !== presec) {
var ssy = dropShadow(germanSec, 0.7).vsa;
var ssx = dropShadow(germanSec, 0.7).hsa;
var sf = '<filter id="sf'+idx+'" x="-50%" y="-50%" width="200%" height="200%">';
var se = '<g filter="url(#sf'+idx+')">';
var ss = '<feOffset id="soffset'+idx+'" dx="'+ssx+'" dy="'+ssy+'" result="offsetblur"/>';
secC.innerHTML = dynShad (sf, ss,se, secsvg);
secHand.appendChild(secC);
}
if (minutes !== premin) {
var msy = dropShadow(minutes * 6, 0.5).vsa;
var msx = dropShadow(minutes * 6, 0.5).hsa;
var mf = '<filter id="mf'+idx+'" x="-50%" y="-50%" width="200%" height="200%">';
var me ='<g filter="url(#mf'+idx+')">';
var ms = '<feOffset id="moffset'+idx+'" dx="'+msx+'" dy="'+msy+'" result="offsetblur"/>';
minC.innerHTML = dynShad (mf, ms,me, minsvg);
minHand.appendChild(minC);
}
if (hours !== prehou) {
var hsy = dropShadow(hours, 0.4).vsa;
var hsx = dropShadow(hours, 0.4).hsa;
var hf = '<filter id="hf'+idx+'" x="-50%" y="-50%" width="200%" height="200%">';
var he ='<g filter="url(#hf'+idx+')">';
var hs = '<feOffset id="hoffset'+idx+'" dx="'+hsx+'" dy="'+hsy+'" result="offsetblur"/>';
houC.innerHTML = dynShad (hf, hs,he, housvg);
houHand.appendChild(houC);
}
secHand.style.transform = 'rotate(' + germanSec + 'deg) translateZ(0)';
minHand.style.transform = 'rotate(' + (minutes * 6) + 'deg) translateZ(0)';
houHand.style.transform = 'rotate(' + hours + 'deg) translateZ(0)';
presec = germanSec;
premin = minutes;
prehou = hours;
tmr = setTimeout(clock, mls);
}
window.addEventListener('load', clock, false);
})();
All I wanted was to dynamically access the filter feOffset's dx and dy
attributes to give the clock hands realistic shadow positions as they
move around the dial.
actually you don't have to touch the dx and dy attribute for a realistic shadow. All you have to do is to put your hands into a g-element and apply the shadow to the group (which is not rotating). in this case the shadow don't rotate and so the offset stays fixed.
see this example, where the rect is rotating, but the shadow is aplied to the group:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="200" height="200">
<defs>
<filter id="gb" filterUnits="userSpaceOnUse" x="-50" y="-50" width="100" height="100">
<feGaussianBlur in="SourceAlpha" stdDeviation="1" />
<feOffset dx="2" dy="2" />
<feMerge>
<feMergeNode />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
<g transform="translate(50 50)" filter="url(#gb)">
<rect x="-0.5" y="-45" width="1" height="45" fill="red">
<animateTransform attributeName="transform" type="rotate" from="0" to="360" dur="10s" repeatCount="indefinite"/>
</rect>
</g>
</svg>
var r=document.getElementById("rect")
setInterval(function(){
var d = new Date();
var a = d.getSeconds()*6; // 360/60 so every second equals 6 deg
r.setAttribute("transform","rotate("+a+")");
},500);
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="200" height="200">
<defs>
<filter id="gb" filterUnits="userSpaceOnUse" x="-50" y="-50" width="100" height="100">
<feGaussianBlur in="SourceAlpha" stdDeviation="1" />
<feOffset dx="2" dy="2" />
<feMerge>
<feMergeNode />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
<g transform="translate(50 50)" filter="url(#gb)">
<rect id="rect" x="-0.5" y="-45" width="1" height="45" fill="red"/>
</g>
</svg>
If you know the id of the filter you can get it using document.getElementById. In your case I think that would be something like this...
var offset = document.getElementById('hoffset'+idx);
You can set and get the dx/dy using setAttribute/getAttribute e.g.
offset.setAttribute("dx", "13");
Or you can use the SVG DOM which will let you work with numbers rather than strings.
offset.dx.baseVal = 13;
German railway T&N station clock
(function () {
/*
German Railway Clock
kurt.grigg#yahoo.co.uk
*/
/* ^^^^^^^^^^^^^^ Config below ^^^^^^^^^^^^^^ */
var clockSize = 300;
var caseColour = 'rgb(20,20,20)';
var dialColour = 'rgba(235,240,240,1.0)';
var sechandColour = 'rgba(173,26,20,1.0)';
var handColour = 'rgb(30,30,30)';
var markColour = 'rgb(10,10,10)';
var reflection = '6Reflection.png';
var clockShape = 50;
/* (max) 50 = round (min) 0 = square */
/* ^^^^^^^^^^^^^^^^ End config ^^^^^^^^^^^^^^ */
var d = document;
var mrkrs = [];
var tmr;
var mls = 50;
var broff = (clockShape < 20) ? 2 : 0;
var prevmin;
var mincr = new Date().getMinutes() - 1;
var hincr = new Date().getHours();
var rnd = 'id'+Math.random() * 1;
var cbcbzr = '.4s cubic-bezier(0.666, 1.91, 0.333, 0)';
var dum = '';
var vb = '<svg height="'+xy(100)+'" width="'+xy(100)+'" viewBox="0 0 200 200">';
d.write('<div id = "'+rnd+'" style="display:inline-block;line-height:0px;"></div>');
function xy (v) {
return (v * clockSize / 100);
}
function genShadKillClone(c, v, x, y) {
c.style.left = xy(x)+'px';
c.style.top = xy(y)+'px';
c.style.zIndex--;
var s = 'filter="url(#handShadow)"';
var r = v.split('filter="url()"').join("");
r = r.replace(/""/g, s);
c.innerHTML = r;
}
/* Clock case and dial */
var outerRim = d.createElement('div');
outerRim.setAttribute('style', 'display:inline-block;'
+'position: relative;'
+'height: '+xy(115)+'px;'
+'width: '+xy(115)+'px;'
+'background-image: linear-gradient(to left, '
+'rgba(0,0,0,0.4) 5%, '
+'rgba(255,255,255,0.3) 50%, '
+'rgba(0,0,0,0.4) 95%);'
+'background-color: '+caseColour+';'
+'border: '+xy(1)+'px solid transparent;'
+'box-shadow: 0 0 '+xy(0.5)+'px '+xy(0.05)+'px rgba(255,255,255,0.7), '
+'0 '+xy(6)+'px '+xy(1)+'px -'+xy(1)+'px rgba(0,0,0,0.6);'
+'border-radius: '+clockShape+'%;');
d.getElementById(rnd).appendChild(outerRim);
var innerRim = d.createElement('div');
innerRim.setAttribute('style', 'display: block;'
+'position: absolute;'
+'height: '+xy(109.8)+'px;'
+'width: '+xy(109.8)+'px;'
+'background-color: '+dialColour+';'
+'margin: auto; top: 0;bottom: 0;left: 0;right: 0;'
+'box-shadow: inset 0 0 0 '+xy(3.0)+'px rgba(50,50,50,0.15),'
+'inset 0 '+xy(6)+'px '+xy(5)+'px '+xy(4)+'px rgba(0,0,0,0.4);'
+'border-radius: '+clockShape+'%;'
+'border: '+xy(0.2)+'px solid rgba(255,255,255,0.05);');
outerRim.appendChild(innerRim);
var dial = d.createElement('div');
dial.setAttribute('style', 'display: block;'
+'position: absolute;'
+'height: '+xy(100)+'px;'
+'width: '+xy(100)+'px;'
+'margin: auto; top: 0; bottom: 0;left: 0;right: 0;'
+'border-radius: '+clockShape+'%;'
+'overflow: hidden;');
innerRim.appendChild(dial);
/* Clock markers */
var face = '<svg id="TNClock" xmlns="http://www.w3.org/2000/svg"'+
'viewBox="0 0 200 200" width="100%" height="100%">'+
'<defs>'+
'<clipPath id="dialPath">'+
'<circle cx="100" cy="100" r="100"/>'+
'</clipPath>'+
'</defs>'+
'<filter id="handShadow" color-interpolation-filters="sRGB">'+
'<feFlood result="flood" flood-color="#000" flood-opacity=".4"/>'+
'<feComposite result="composite1" operator="in" in2="SourceGraphic" in="flood"/>'+
'<feGaussianBlur result="blur" stdDeviation="0.5" in="composite1"/>'+
'<feOffset result="offset" dy="0" dx="0"/>'+
'<feComposite result="composite2" operator="atop" in2="offset" in="offset"/>'+
'</filter>'+
'<g id="TNLogo" transform="translate(87.4,141.2) scale(0.5, 0.5)">'+
'<path stroke="#000" fill="none" d="M2.732 19L25 2.06 47.268 19 25 35.94 2.732 19z"/>'+
'<path d="M17.194 10.2h15.612v2.298h-6.713v17.727h-2.186V12.498h-6.713V10.2z"/>'+
'<path d="M17.975 14.95l11.865 8.864V14.95h2.185v13.13L20.16 19.22v8.863h-2.185V14.95z"/>'+
'</g>'+
'</svg>';
dial.innerHTML = face;
for (var i = 0; i < 60; i++) {
var mrkrLength = 30;
if (i % 15) {
mrkrLength = 24;
}
if (i % 5) {
mrkrLength = 8;
}
var mrkrWidth = (i % 5) ? 3.6 : 8.8;
mrkrs[i] = document.createElementNS("http://www.w3.org/2000/svg", 'line');
with(mrkrs[i]) {
setAttribute('x1', '100');
setAttribute('y1', '0');
setAttribute('x2', '100');
setAttribute('y2', mrkrLength);
setAttribute('stroke', markColour);
setAttribute('stroke-width', mrkrWidth);
setAttribute('stroke-linecap', 'butt');
setAttribute("clip-path", "url(#dialPath)");
setAttribute( "transform","rotate("+i * 6+", 100, 100)");
}
TNClock.appendChild(mrkrs[i]);
}
/* Generic container for all hands */
var handContainers = 'display: block;'
+'position: absolute;'
+'height: '+xy(100)+'px;'
+'width: '+xy(100)+'px;'
+'font-size: 0px; line-height: 0px; padding: 0;'
+'margin: auto; top: 0;bottom: 0; left: 0; right: 0;'
+'transform-origin: center center;'
/* Hour hand */
var houContainer = d.createElement('div');
houContainer.setAttribute('style', handContainers + 'transition: '+cbcbzr+';');
houContainer.style.zIndex = 50;
dial.appendChild(houContainer);
var houHand = d.createElement('div');
var housvg = vb +
'<polygon points="94,46 100,40 106,46 106,118 94,118" fill="'+handColour+'" stroke="none" "'+dum+'" />'+
'</svg>';
houHand.innerHTML = housvg;
houContainer.appendChild(houHand);
var houShad = houContainer.cloneNode(true);
dial.appendChild(houShad);
genShadKillClone(houShad,housvg, 0, 3);
/* Minute hand */
var minContainer = d.createElement('div');
minContainer.setAttribute('style',handContainers + 'transition: '+cbcbzr+';');
minContainer.style.zIndex = 52;
dial.appendChild(minContainer);
var minHand = d.createElement('div');
var minsvg = vb +
'<polygon points="95.5,11.5 100,7 104.5,11.5 104.5,122 95.5,122" fill="'+handColour+'" stroke="none" "'+dum+'" />'+
'</svg>';
minHand.innerHTML = minsvg;
minContainer.appendChild(minHand);
var minShad = minContainer.cloneNode(true);
dial.appendChild(minShad);
genShadKillClone(minShad,minsvg, 0, 4);
/* Seconds hand */
var secContainer = d.createElement('div');
secContainer.setAttribute('style',handContainers);
secContainer.style.zIndex = 54;
dial.appendChild(secContainer);
var secHand = d.createElement('div');
var secsvg = vb +
'<path d="M100 9.05l-1.2 1.2-.355 27.367c-5.724.77-10.195 5.71-10.195 11.633 0 5.792 4.275 10.643 9.816 11.576L97.5 121.25h5l-.568-60.424c5.54-.933 9.818-5.784 9.818-11.576 0-5.923-4.473-10.862-10.197-11.633L101.2 10.25zM100 44c2.938 0 5.25 2.312 5.25 5.25s-2.312 5.25-5.25 5.25-5.25-2.312-5.25-5.25S97.062 44 100 44z" fill="'+sechandColour+'" stroke="none" stroke-width="0" "'+dum+'" />'+
'</svg>';
secHand.innerHTML = secsvg;
secContainer.appendChild(secHand);
var secShad = secContainer.cloneNode(true);
dial.appendChild(secShad);
genShadKillClone(secShad,secsvg, 0, 5);
var glass = d.createElement('div');
glass.setAttribute('style', 'display: block;'
+'position: absolute;'
+'height: '+xy(108.8)+'px;'
+'width: '+xy(108.8)+'px;'
+'margin: auto; top: 0; bottom: 0; left: 0;right: 0;'
+'border: '+xy(0.2)+'px solid #000;'
+'border-radius: '+clockShape+'%;'
+'background-image: url("http://i61.tinypic.com/300xjcn.png");'
+'background-size: cover;'
+'opacity: 0.3;'
+'z-index: 55;'
+'overflow: hidden;');
innerRim.appendChild(glass);
function clock() {
var x = new Date();
var time = Math.min(60000, 1.025 * (1000 * x.getSeconds() + x.getMilliseconds()));
var seconds = Math.floor(time / 1000);
var millis = time % 1000;
var germanSec = (6 * seconds + 3 * (1 + Math.cos(Math.PI + Math.PI * (0.001 * millis))));
var presmin = x.getMinutes();
if (presmin !== prevmin) {
mincr++;
}
prevmin = presmin;
secContainer.style.transform = 'rotate(' + germanSec + 'deg) translateZ(0)';
secShad.style.transform = 'rotate(' + germanSec + 'deg) translateZ(0)';
minContainer.style.transform = 'rotate(' + (mincr * 6) + 'deg) translateZ(0)';
minShad.style.transform = 'rotate(' + (mincr * 6) + 'deg) translateZ(0)';
houContainer.style.transform = 'rotate(' + ((hincr * 30) + (mincr / 2)) + 'deg) translateZ(0)';
houShad.style.transform = 'rotate(' + ((hincr * 30) + (mincr / 2)) + 'deg) translateZ(0)';
tmr = setTimeout(clock, mls);
}
window.addEventListener('load', clock, false);
})();
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>German Railway Clock</title>
<style type="text/css">
body {
background-color: rgb(205,179,139);
text-align: center;
}
</style>
</head>
<body>
</body>
</html>
The Original Hilfiker/MobaTime Swiss Railway Clock. 1953 version.
(function () {
/*
The Hilfiker/MobaTime Swiss Railway Clock
1953 version (Stop To Go)
kurt.grigg#yahoo.co.uk
*/
/* ^^^^^^^^^^^^^^ Config below ^^^^^^^^^^^^^^ */
var clockSize = 300;
var caseColour = 'rgba(200,200,200,1.0)';
var dialColour = 'rgba(235,240,240,1.0)';
var sechandColour = 'rgba(173,26,20,1.0)';
var handColour = 'rgb(20,20,20)';
var markColour = 'rgb(10,10,10)';
var reflection = '6Reflection.png';
var clockShape = 50;
/* (max) 50 = round (min) 0 = square */
/* ^^^^^^^^^^^^^^^^ End config ^^^^^^^^^^^^^^ */
var d = document;
var mrkrs = [];
var tmr;
var mls = 50;
var broff = (clockShape < 20) ? 2 : 0;
var prevmin;
var mincr = new Date().getMinutes() - 1;
var hincr = new Date().getHours();
var rnd = 'id'+Math.random() * 1;
var cbcbzr = '.3s cubic-bezier(0.666, 1.91, 0.333, 0)';
var vb = '<svg height="'+xy(100)+'" width="'+xy(100)+'" viewBox="0 0 200 200">';
d.write('<div id = "'+rnd+'" style="display:inline-block;line-height:0px;"></div>');
var dum = '';
function xy (v) {
return (v * clockSize / 100);
}
function genShadKillClone(c, v, x, y) {
c.style.left = xy(x)+'px';
c.style.top = xy(y)+'px';
c.style.zIndex--;
var s = 'filter="url(#handShadow)"';
var r = v.split('filter="url()"').join("");
r = r.replace(/""/g, s);
c.innerHTML = r;
}
/* Clock case and dial */
var outerRim = d.createElement('div');
outerRim.setAttribute('style', 'display:inline-block;'
+'position: relative;'
+'height: '+xy(116)+'px;'
+'width: '+xy(116)+'px;'
+'background-image: linear-gradient(to left, '
+'rgba(0,0,0,0.3) 0%, '
+'rgba(255,255,255,0.6) 50%, '
+'rgba(0,0,0,0.3) 100%);'
+'background-color: '+caseColour+';'
+'border: '+xy(1)+'px solid transparent;'
+'box-shadow: 0 0 '+xy(0.5)+'px '+xy(0.05)+'px rgba(255,255,255,0.7), '
+'0 '+xy(6)+'px '+xy(1)+'px -'+xy(1)+'px rgba(0,0,0,0.6);'
+'border-radius: '+clockShape+'%;');
d.getElementById(rnd).appendChild(outerRim);
var innerRim = d.createElement('div');
innerRim.setAttribute('style', 'display: block;'
+'position: absolute;'
+'height: '+xy(113.8)+'px;'
+'width: '+xy(113.8)+'px;'
+'background-color: '+dialColour+';'
+'margin: auto; top: 0;bottom: 0;left: 0;right: 0;'
+'box-shadow: inset 0 0 0 '+xy(2.9)+'px rgba(30,30,30,0.3),'
+'inset 0 '+xy(3)+'px '+xy(5)+'px '+xy(3)+'px rgba(0,0,0,0.6);'
+'border-radius: '+clockShape+'%;'
+'border: '+xy(0.2)+'px solid rgba(255,255,255,0.05);');
outerRim.appendChild(innerRim);
var dial = d.createElement('div');
dial.setAttribute('style', 'display: block;'
+'position: absolute;'
+'height: '+xy(100)+'px;'
+'width: '+xy(100)+'px;'
+'margin: auto; top: 0; bottom: 0;left: 0;right: 0;'
+'border-radius: '+clockShape+'%;'
+'overflow: hidden;');
innerRim.appendChild(dial);
/* Clock markers */
var face = '<svg id="MobaTimeClock" xmlns="http://www.w3.org/2000/svg"'+
'viewBox="0 0 200 200" width="100%" height="100%">'+
'<defs>'+
'<clipPath id="dialPath">'+
'<circle cx="100" cy="100" r="100"/>'+
'</clipPath>'+
'</defs>'+
'<filter id="handShadow" color-interpolation-filters="sRGB">'+
'<feFlood result="flood" flood-color="#000" flood-opacity=".4"/>'+
'<feComposite result="composite1" operator="in" in2="SourceGraphic" in="flood"/>'+
'<feGaussianBlur result="blur" stdDeviation="0.5" in="composite1"/>'+
'<feOffset result="offset" dy="0" dx="0"/>'+
'<feComposite result="composite2" operator="atop" in2="offset" in="offset"/>'+
'</filter>'+
'</svg>';
dial.innerHTML = face;
for (var i = 0; i < 60; i++) {
var mrkrLength = (i % 5) ? 7.5 : 25;
var mrkrWidth = (i % 5) ? 3 : 7.5;
mrkrs[i] = document.createElementNS("http://www.w3.org/2000/svg", 'line');
with(mrkrs[i]) {
setAttribute('x1', '100');
setAttribute('y1', '0');
setAttribute('x2', '100');
setAttribute('y2', mrkrLength);
setAttribute('stroke', markColour);
setAttribute('stroke-width', mrkrWidth);
setAttribute('stroke-linecap', 'butt');
setAttribute("clip-path", "url(#dialPath)");
setAttribute( "transform","rotate("+i * 6+", 100, 100)");
}
MobaTimeClock.appendChild(mrkrs[i]);
}
var logo = '<svg width="100%" height="100%" viewBox="0 0 160 106" ' +
'xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">' +
'<path d="M72.764.744C74.936.347 77.136 0 79.35.052c.94.017 1.88.01 2.82.08 4.8' +
'35.364 9.615 1.48 14.098 3.357 4.685 1.99 9.022 4.85 12.635 8.467 3.488 3.46 6' +
'.303 7.605 8.27 12.123 1.837 4.26 2.964 8.834 3.284 13.47.3 4.69-.077 9.427-1.' +
'208 13.988-.398 1.737-.95 3.43-1.494 5.126-3.685-2.417-7.306-4.932-10.977-7.37' +
'.638-2.657 1.03-5.388.955-8.128-.032-2.894-.504-5.78-1.35-8.545-1.36-4.383-3.75' +
'-8.47-7.038-11.658-3.758-3.765-8.715-6.24-13.914-7.168-2.006-.367-4.047-.537-6' +
'.083-.51-2.38.167-4.755.56-7.04 1.256-.07.018-.212.05-.282.067-4.875 1.52-9.37' +
' 4.36-12.692 8.292-3.532 4.122-5.726 9.365-6.367 14.77-.3 2.187-.26 4.41-.16 6' +
'.61.152 1.61.355 3.218.707 4.797-3.644 2.364-7.266 4.76-10.93 7.09-1.812-4.79-' +
'2.977-9.862-3.083-15.002-.237-8.587 2.43-17.226 7.466-24.152C51.114 11.27 56.7' +
'94 6.69 63.212 3.79c3.06-1.366 6.266-2.402 9.552-3.046z" fill-opacity=".9" fill="'+markColour+'"/>' +
'<path d="M48.772 76.595L55.83 71.9l1.774 1.816c1.023 1.047 2.286 2.145 2.984 2' +
'.595 5.233 3.37 12.434 5.067 20.513 4.835 3.703-.106 5.01-.245 8.036-.856 2.72' +
'-.55 5.2-1.387 7.582-2.557 2.47-1.213 4.22-2.505 5.976-4.41l1.357-1.47 7.045 4' +
'.715 7.045 4.715 11.09.06c6.934.038 11.03.002 10.926-.096-.828-.782-35.83-26.7' +
'53-35.916-26.648-4.85 5.9-8.918 9.252-13.73 11.313-3.472 1.487-6.387 2.055-10.' +
'566 2.058-9.514.007-16.507-3.84-24.292-13.368-.08-.096-31.817 23.46-35.66 26.4' +
'67-.395.308-.217.312 10.66.266l11.064-.047 7.056-4.695z" fill="url(#a)"/>' +
'<path d="M81.42 90.82h-3.505l-5.58 15.26h3.473l1.078-2.32 5.597.02 1.08 2.302h' +
'3.32l-5.462-15.26zm-1.717 4.213l1.667 5.944h-3.285l1.618-5.944zm-22.434-4.23h-' +
'5.174v15.26l6.303.016c1.6.02 3.624-1.607 3.994-3.73.084-2.25-.845-4.16-2.376-4' +
'.446.876-.41 1.45-1.89 1.348-3.854-.238-1.944-2.26-3.392-4.097-3.248m.976 3.73' +
'c.353.662.153 1.337-.05 1.837-.656.768-1.837.714-2.78.714v-3.302c.842 0 2.308-' +
'.16 2.83.75m.625 5.747c.37.606.286 1.73.103 2.16-.893.998-2.16.962-3.523.8v-3.' +
'604c1.618-.108 2.46-.09 3.42.643M15.118 90.8l2.41 15.24-3 .037-1.432-8.89-3.75' +
'8 8.89-.926-.037-3.675-8.496-1.517 8.532L0 106.042l2.36-15.277h3.17l3.166 8.744' +
' 3.42-8.708h3zm19.23 11.673c-2.29 0-4.18-2-4.18-4.427 0-2.427 1.89-4.425 4.18-' +
'4.425 2.293 0 4.18 1.998 4.18 4.425 0 2.426-1.887 4.427-4.18 4.427m0 3.604c4.2' +
'8 0 7.77-3.605 7.77-8.032 0-4.425-3.49-8.048-7.77-8.048-4.28 0-7.768 3.623-7.7' +
'68 8.048 0 4.427 3.488 8.03 7.768 8.03M98.68 91.482h8.56l-.285 1.68h-3.49l-1.8' +
'87 12.883h-1.754l1.924-12.884h-3.374l.306-1.68zm18.623 0h1.753l-2.19 14.563h-1' +
'.738l2.175-14.562zm24.506-1.017l.756 15.58h-1.99l-.115-9.94-5.898 10.242-2.715' +
'-10.242-3.166 9.94h-1.972l5.444-15.58 3 11.796 6.657-11.797zm18.236 1.018l-.43' +
'8 1.68h-5.648l-.608 4.086h5.546l-.32 1.66h-5.494l-.876 5.46h5.798l-.286 1.677h' +
'-7.55l2.174-14.562h7.702z" fill-opacity="1.0" fill="'+markColour+'"/>' +
'<defs>' +
'<pattern xlink:href="#b" id="a" patternTransform="translate(364.52 610.068) scale(6.07904)"/>' +
'<pattern id="b" patternTransform="scale(10)" height="1" width="1.07" patternUnits="userSpaceOnUse">' +
'<path fill="'+markColour+'" d="M0 0h.5v1H0z"/>' +
'</pattern>' +
'</defs>' +
'</svg>';
var mtl = d.createElement('div');
mtl.setAttribute('style', 'display: block;'
+'position: absolute;'
+'height: '+xy(9)+'px;'
+'top: '+xy(68)+'px;'
+'paddingBottom: '+xy(1)+'px;'
+'margin: auto; left: 0;right: 0;');
mtl.innerHTML = logo;
dial.appendChild(mtl);
/* Generic container for all hands */
var handContainers = 'display: block;'
+'position: absolute;'
+'height: '+xy(100)+'px;'
+'width: '+xy(100)+'px;'
+'font-size: 0px; line-height: 0px; padding: 0;'
+'margin: auto; top: 0;bottom: 0; left: 0; right: 0;'
+'transform-origin: center center;'
/* Hour hand */
var houContainer = d.createElement('div');
houContainer.setAttribute('style', handContainers + 'transition: '+cbcbzr+';');
houContainer.style.zIndex = 50;
dial.appendChild(houContainer);
var houHand = d.createElement('div');
var housvg = vb +
'<polygon points="95,33 105,33 106,125 94,125" transform="translate(0,2)" fill="'+handColour+'" stroke="none" "'+dum+'" />'+
'</svg>';
houHand.innerHTML = housvg;
houContainer.appendChild(houHand);
var houShad = houContainer.cloneNode(true);
dial.appendChild(houShad);
genShadKillClone(houShad,housvg, 0, 3);
/* Minute hand */
var minContainer = d.createElement('div');
minContainer.setAttribute('style',handContainers + 'transition: '+cbcbzr+';');
minContainer.style.zIndex = 52;
dial.appendChild(minContainer);
var minHand = d.createElement('div');
var minsvg = vb +
'<polygon points="96,5 104,5 105,125 95,125" transform="translate(0,2)" fill="'+handColour+'" stroke="none" "'+dum+'" />'+
'</svg>';
minHand.innerHTML = minsvg;
minContainer.appendChild(minHand);
var minShad = minContainer.cloneNode(true);
dial.appendChild(minShad);
genShadKillClone(minShad,minsvg, 0, 4);
/* Seconds hand */
var secContainer = d.createElement('div');
secContainer.setAttribute('style',handContainers);
secContainer.style.zIndex = 54;
dial.appendChild(secContainer);
var secHand = d.createElement('div');
var secsvg = vb +
'<path d="M100 23.475a10 10 0 0 0-10 10 10 10 0 0 0 8.22 9.832V135h3.56V43.31a10 10 0 0 0 8.22-9.835 10 10 0 0 0-10-10z" transform="translate(0,1)" fill="'+sechandColour+'" "'+dum+'" />'+
'</svg>';
secHand.innerHTML = secsvg;
secContainer.appendChild(secHand);
var secShad = secContainer.cloneNode(true);
dial.appendChild(secShad);
genShadKillClone(secShad,secsvg, 0, 5);
var glass = d.createElement('div');
glass.setAttribute('style', 'display: block;'
+'position: absolute;'
+'height: '+xy(112)+'px;'
+'width: '+xy(112)+'px;'
+'margin: auto; top: 0; bottom: 0; left: 0;right: 0;'
+'border-radius: '+clockShape+'%;'
+'background-image: url("http://i61.tinypic.com/300xjcn.png");'
+'background-size: cover;'
+'opacity: 0.3;'
+'z-index: 55;'
+'overflow: hidden;');
innerRim.appendChild(glass);
function clock() {
var x = new Date();
var StopToGo = (Math.min((x.getSeconds() + x.getMilliseconds() / 1000) * (60 / 58.5), 60));
var presmin = x.getMinutes();
if (presmin !== prevmin) {
mincr++;
}
prevmin = presmin;
secContainer.style.transform = 'rotate(' + (StopToGo * 6) + 'deg) translateZ(0)';
secShad.style.transform = 'rotate(' + (StopToGo * 6) + 'deg) translateZ(0)';
minContainer.style.transform = 'rotate(' + (mincr * 6) + 'deg) translateZ(0)';
minShad.style.transform = 'rotate(' + (mincr * 6) + 'deg) translateZ(0)';
houContainer.style.transform = 'rotate(' + ((hincr * 30) + (mincr / 2)) + 'deg) translateZ(0)';
houShad.style.transform = 'rotate(' + ((hincr * 30) + (mincr / 2)) + 'deg) translateZ(0)';
tmr = setTimeout(clock, mls);
}
window.addEventListener('load', clock, false);
})();
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Hilfiker MobaTime Swiss Railway Clock</title>
<style type="text/css">
body {
background-color: rgb(205,179,139);
text-align: center;
}
</style>
</head>
<body>
</body>
</html>
Deutsche Bahn Clock - Modern.
(function () {
/*
Deutsche Bahn Clock
kurt.grigg#yahoo.co.uk
*/
/* ^^^^^^^^^^^^^^ Config below ^^^^^^^^^^^^^^ */
var clockSize = 300;
var caseColour = 'rgb(0,0,120)';
var dialColour = 'rgba(235,240,240,1.0)';
var sechandColour = 'rgba(173,26,20,1.0)';
var handColour = 'rgb(30,30,30)';
var markColour = 'rgb(10,10,10)';
var handShadowColour = 'rgba(0,0,0,0.3)';
var reflection = '6Reflection.png';
var clockShape = 50;
/* (max) 50 = round (min) 0 = square */
/* ^^^^^^^^^^^^^^^^ End config ^^^^^^^^^^^^^^ */
var d = document;
var mrkrs = [];
var tmr;
var mls = 50;
var broff = (clockShape < 20) ? 2 : 0;
var prevmin;
var mincr = new Date().getMinutes() - 1;
var hincr = new Date().getHours();
var rnd = 'id'+Math.random() * 1;
var dum = '';
var cbcbzr = '.3s cubic-bezier(0.666, 1.91, 0.333, 0)';
var vb = '<svg height="'+xy(100)+'" width="'+xy(100)+'" viewBox="0 0 200 200">';
d.write('<div id = "'+rnd+'" style="display:inline-block;line-height:0px;"></div>');
function xy (v) {
return (v * clockSize / 100);
}
function genShadKillClone(c, v, x, y) {
c.style.left = xy(x)+'px';
c.style.top = xy(y)+'px';
c.style.zIndex--;
var s = 'filter="url(#handShadow)"';
var r = v.split('filter="url()"').join("");
r = r.replace(/""/g, s);
c.innerHTML = r;
}
/* Clock case and dial */
var outerRim = d.createElement('div');
outerRim.setAttribute('style', 'display:inline-block;'
+'position: relative;'
+'height: '+xy(115)+'px;'
+'width: '+xy(115)+'px;'
+'background-image: linear-gradient(to left, '
+'rgba(0,0,0,0.6) 5%, '
+'rgba(255,255,255,0.2) 50%, '
+'rgba(0,0,0,0.6) 95%);'
+'background-color: '+caseColour+';'
+'border: '+xy(0.5)+'px solid transparent;'
+'box-shadow: inset 0 0 '+xy(2)+'px '+xy(0.3)+'px rgba(255,255,255,0.5),'
+' 0 '+xy(6)+'px '+xy(1)+'px -'+xy(1)+'px rgba(0,0,0,0.6);'
+'border-radius: '+clockShape+'%;');
d.getElementById(rnd).appendChild(outerRim);
var innerRim = d.createElement('div');
innerRim.setAttribute('style', 'display: block;'
+'position: absolute;'
+'height: '+xy(109.8)+'px;'
+'width: '+xy(109.8)+'px;'
+'background-color: '+dialColour+';'
+'margin: auto; top: 0;bottom: 0;left: 0;right: 0;'
+'box-shadow: inset 0 0 0 '+xy(3.0)+'px rgba(50,50,50,0.15),'
+'inset 0 '+xy(6)+'px '+xy(5)+'px '+xy(4)+'px rgba(0,0,0,0.4);'
+'border-radius: '+clockShape+'%;'
+'border: '+xy(0.2)+'px solid rgba(255,255,255,0.05);');
outerRim.appendChild(innerRim);
var dial = d.createElement('div');
dial.setAttribute('style', 'display: block;'
+'position: absolute;'
+'height: '+xy(100)+'px;'
+'width: '+xy(100)+'px;'
+'margin: auto; top: 0; bottom: 0;left: 0;right: 0;'
+'border-radius: '+clockShape+'%;'
+'overflow: hidden;');
innerRim.appendChild(dial);
/* Clock markers */
var face = '<svg id="DBClock" xmlns="http://www.w3.org/2000/svg"'+
'viewBox="0 0 200 200" width="100%" height="100%">'+
'<defs>'+
'<clipPath id="dialPath">'+
'<circle cx="100" cy="100" r="100"/>'+
'</clipPath>'+
'</defs>'+
'<filter id="handShadow" color-interpolation-filters="sRGB">'+
'<feFlood result="flood" flood-color="#000" flood-opacity=".4"/>'+
'<feComposite result="composite1" operator="in" in2="SourceGraphic" in="flood"/>'+
'<feGaussianBlur result="blur" stdDeviation="0.5" in="composite1"/>'+
'<feOffset result="offset" dy="0" dx="0"/>'+
'<feComposite result="composite2" operator="atop" in2="offset" in="offset"/>'+
'</filter>'+
'<g id="DBLogo" transform="translate(89.2,35.5) scale(0.5, 0.5)">'+
'<path fill="none" d="M40.26 27.83H2.49V1.964h37.77V27.83z"/>'+
'<path fill="'+sechandColour+'" fill-rule="evenodd" d="M5.916'+
' 5.233h7.566c4.72 0 7.494 3.044 7.494 9.614 0 6.997-2.9 9.8-'+
'7.494 9.8H5.916V5.232zm17.18 0h8.46c3.542 0 5.32 2.134 5.32 '+
'4.594 0 3.228-1.892 4.323-3.356 4.636v.085c2.19.285 4.167 1.'+
'835 4.167 4.807-.015 3.2-2.333 5.29-6.656 5.29h-7.935V5.234z'+
'm-12.5 3.044h1.663c2.687 0 4.11 1.934 4.11 6.485 0 5.22-1.66'+
'5 6.783-4.026 6.783h-1.75V8.277zm17.136-.03h1.79c1.836 0 2.5'+
'9 1.025 2.59 2.22 0 1.45-.782 2.46-2.617 2.46h-1.763v-4.68zm'+
'0 8.335h2.204c2.176 0 2.76 1.052 2.76 2.432 0 1.607-1.167 2.'+
'53-2.732 2.53h-2.232v-4.962zM4.394 0h34.203c2.432 0 4.408 1.'+
'963 4.408 4.394v20.99c0 2.433-1.976 4.41-4.408 4.41H4.394c-2'+
'.43 0-4.394-1.977-4.394-4.41V4.395C0 1.964 1.963 0 4.394 0zm'+
'.057 3h34.104c.754 0 1.365.612 1.365 1.366v21.048c0 .753-.61'+
'2 1.365-1.366 1.365H4.45c-.752 0-1.364-.613-1.364-1.366V4.36'+
'6C3.086 3.612 3.698 3 4.45 3"/></g></svg>';
dial.innerHTML = face;
for (var i = 0; i < 60; i++) {
var mrkrLength = 30;
if (i % 15) {
mrkrLength = 24;
}
if (i % 5) {
mrkrLength = 8;
}
var mrkrWidth = (i % 5) ? 3.6 : 8.8;
mrkrs[i] = document.createElementNS("http://www.w3.org/2000/svg", 'line');
with(mrkrs[i]) {
setAttribute('x1', '100');
setAttribute('y1', '0');
setAttribute('x2', '100');
setAttribute('y2', mrkrLength);
setAttribute('stroke', markColour);
setAttribute('stroke-width', mrkrWidth);
setAttribute('stroke-linecap', 'butt');
setAttribute("clip-path", "url(#dialPath)");
setAttribute( "transform","rotate("+i * 6+", 100, 100)");
}
DBClock.appendChild(mrkrs[i]);
}
/* Generic container for all hands */
var handContainers = 'display: block;'
+'position: absolute;'
+'height: '+xy(100)+'px;'
+'width: '+xy(100)+'px;'
+'font-size: 0px; line-height: 0px; padding: 0;'
+'margin: auto; top: 0;bottom: 0; left: 0; right: 0;'
+'transform-origin: center center;'
/* Hour hand */
var houContainer = d.createElement('div');
houContainer.setAttribute('style', handContainers + 'transition: '+cbcbzr+';');
houContainer.style.zIndex = 50;
dial.appendChild(houContainer);
var houHand = d.createElement('div');
var housvg = vb +
'<rect x="95" y="40" width="10" height="58" stroke="none" fill="'+handColour+'" "'+dum+'" />'+
'</svg>';
houHand.innerHTML = housvg;
houContainer.appendChild(houHand);
var houShad = houContainer.cloneNode(true);
dial.appendChild(houShad);
genShadKillClone(houShad,housvg, 0, 3);
/* Minute hand */
var minContainer = d.createElement('div');
minContainer.setAttribute('style',handContainers + 'transition: '+cbcbzr+';');
minContainer.style.zIndex = 52;
dial.appendChild(minContainer);
var minHand = d.createElement('div');
var minsvg = vb +
'<rect x="96" y="6" width="8" height="92" fill="'+handColour+'" stroke="none" "'+dum+'" />'+
'</svg>';
minHand.innerHTML = minsvg;
minContainer.appendChild(minHand);
var minShad = minContainer.cloneNode(true);
dial.appendChild(minShad);
genShadKillClone(minShad,minsvg, 0, 4);
/* Seconds hand */
var secContainer = d.createElement('div');
secContainer.setAttribute('style',handContainers);
secContainer.style.zIndex = 54;
dial.appendChild(secContainer);
var secHand = d.createElement('div');
var secsvg = vb +
'<path d="M98 2l-.275 29.223C92.2 32.293 88 37.173 88 43c0 5.744 4'+
'.084 10.57 9.492 11.73L97 100h6l-.482-45.27C107.92 53.563 112 48.'+
'74 112 43c0-5.826-4.203-10.707-9.727-11.777L102 2h-4zm2 33c4.442 '+
'0 8 3.558 8 8s-3.558 8-8 8-8-3.558-8-8 3.558-8 8-8z" fill="'+sechandColour+'" "'+dum+'"/></svg>';
secHand.innerHTML = secsvg;
secContainer.appendChild(secHand);
var secShad = secContainer.cloneNode(true);
dial.appendChild(secShad);
genShadKillClone(secShad,secsvg, 0, 5);
var nut = d.createElement('div');
nut.setAttribute('style', 'display: block;'
+'position: absolute;'
+'height: '+xy(10)+'px;'
+'width: '+xy(10)+'px;'
+'border-radius: 50%;'
+'margin: auto;top: 0;bottom: 0;left: 0;right: 0;'
+'background: '+handColour+';'
+'box-shadow: 0 '+xy(2)+'px '+xy(1)+'px rgba(0,0,0,0.35);'
+'z-index: 56;');
innerRim.appendChild(nut);
var glass = d.createElement('div');
glass.setAttribute('style', 'display: block;'
+'position: absolute;'
+'height: '+xy(108.8)+'px;'
+'width: '+xy(108.8)+'px;'
+'margin: auto; top: 0; bottom: 0; left: 0;right: 0;'
+'border: '+xy(0.2)+'px solid #000;'
+'border-radius: '+clockShape+'%;'
+'background-image: url("http://i61.tinypic.com/300xjcn.png");'
+'background-size: cover;'
+'opacity: 0.25;'
+'z-index: 57;'
+'overflow: hidden;');
innerRim.appendChild(glass);
function clock() {
var x = new Date();
var time = Math.min(60000, 1.025 * (1000 * x.getSeconds() + x.getMilliseconds()));
var seconds = Math.floor(time / 1000);
var millis = time % 1000;
var germanSec = (6 * seconds + 3 * (1 + Math.cos(Math.PI + Math.PI * (0.001 * millis))));
var presmin = x.getMinutes();
if (presmin !== prevmin) {
mincr++;
}
prevmin = presmin;
secContainer.style.transform = 'rotate(' + germanSec + 'deg) translateZ(0)';
secShad.style.transform = 'rotate(' + germanSec + 'deg) translateZ(0)';
minContainer.style.transform = 'rotate(' + (mincr * 6) + 'deg) translateZ(0)';
minShad.style.transform = 'rotate(' + (mincr * 6) + 'deg) translateZ(0)';
houContainer.style.transform = 'rotate(' + ((hincr * 30) + (mincr / 2)) + 'deg) translateZ(0)';
houShad.style.transform = 'rotate(' + ((hincr * 30) + (mincr / 2)) + 'deg) translateZ(0)';
tmr = setTimeout(clock, mls);
}
window.addEventListener('load', clock, false);
})();
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Deutsche Bahn Clock</title>
<style type="text/css">
body {
background-color: rgb(205,179,139);
text-align: center;
}
</style>
</head>
<body>
</body>
</html>
The Hilfiker/MobaTime Swiss Railway Clock
Exaggerated step and bounce on all hands
(function () {
/*
The Hilfiker/MobaTime Swiss Railway Clock
1953 version (Stop To Go)
(Exaggerated) step and bounce on all hands
kurt.grigg#yahoo.co.uk
*/
/* ^^^^^^^^^^^^^^ Config below ^^^^^^^^^^^^^^ */
var clockSize = 300;
var caseColour = 'rgba(200,200,200,1.0)';
var dialColour = 'rgba(235,240,240,1.0)';
var sechandColour = 'rgba(173,26,20,1.0)';
var handColour = 'rgb(20,20,20)';
var markColour = 'rgb(10,10,10)';
var handShadowColour = 'rgba(0,0,0,0.3)';
var reflection = '6Reflection.png';
var clockShape = 50;
/* (max) 50 = round (min) 0 = square */
/* ^^^^^^^^^^^^^^^^ End config ^^^^^^^^^^^^^^ */
var mls = 100;
var secSpan = '.8s';
var minSpan = '1.0s';
var houSpan = '1.2s';
var secIncr = 0;
var minIncr = 0;
var houIncr = 0;
var d = document;
var mrkrs = [];
var broff = (clockShape < 20) ? 2 : 0;
var rndId = 'id'+Math.random() * 1;
var idx = d.getElementsByTagName('div').length;
var secDeg, minDeg, houDeg, preSec, preMin, preHou;
var rnd = 'id'+Math.random() * 1;
var dum = '';
var vb = '<svg height="'+xy(100)+'" width="'+xy(100)+'" viewBox="0 0 200 200">';
var mobasec = new Date().getSeconds();
var mobaoffset = (58.5/60);
var initcyc = mobaoffset * mobasec;
var mobacycle = 1000 - initcyc;
var tmr, mobaTimer;
var firstrun = true;
var eiatf = 'translateZ(0); animation-timing-function: ease-in';
var eoatf = 'translateZ(0); animation-timing-function: ease-out';
d.write('<div id = "'+rnd+'" style="display:inline-block;line-height:0px;"></div>');
function xy (v) {
return (v * clockSize / 100);
}
function genShadKillClone(c, v, x, y) {
c.style.left = xy(x)+'px';
c.style.top = xy(y)+'px';
c.style.zIndex--;
var s = 'filter="url(#handShadow)"';
var r = v.split('filter="url()"').join("");
r = r.replace(/""/g, s);
c.innerHTML = r;
}
/* Clock case and dial */
var outerRim = d.createElement('div');
outerRim.setAttribute('style', 'display:inline-block;'
+'position: relative;'
+'height: '+xy(116)+'px;'
+'width: '+xy(116)+'px;'
+'background-image: linear-gradient(to left, '
+'rgba(0,0,0,0.3) 0%, '
+'rgba(255,255,255,0.6) 50%, '
+'rgba(0,0,0,0.3) 100%);'
+'background-color: '+caseColour+';'
+'border: '+xy(1)+'px solid transparent;'
+'box-shadow: 0 0 '+xy(0.5)+'px '+xy(0.05)+'px rgba(255,255,255,0.7), '
+'0 '+xy(6)+'px '+xy(1)+'px -'+xy(1)+'px rgba(0,0,0,0.6);'
+'border-radius: '+clockShape+'%;');
d.getElementById(rnd).appendChild(outerRim);
var innerRim = d.createElement('div');
innerRim.setAttribute('style', 'display: block;'
+'position: absolute;'
+'height: '+xy(113.8)+'px;'
+'width: '+xy(113.8)+'px;'
+'background-color: '+dialColour+';'
+'margin: auto; top: 0;bottom: 0;left: 0;right: 0;'
+'box-shadow: inset 0 0 0 '+xy(2.9)+'px rgba(30,30,30,0.3),'
+'inset 0 '+xy(3)+'px '+xy(5)+'px '+xy(3)+'px rgba(0,0,0,0.6);'
+'border-radius: '+clockShape+'%;'
+'border: '+xy(0.2)+'px solid rgba(255,255,255,0.05);');
outerRim.appendChild(innerRim);
var dial = d.createElement('div');
dial.setAttribute('style', 'display: block;'
+'position: absolute;'
+'height: '+xy(100)+'px;'
+'width: '+xy(100)+'px;'
+'margin: auto; top: 0; bottom: 0;left: 0;right: 0;'
+'border-radius: '+clockShape+'%;'
+'overflow: hidden;');
innerRim.appendChild(dial);
/* Clock markers */
var face = '<svg id="MobaTimeClock" xmlns="http://www.w3.org/2000/svg"'+
'viewBox="0 0 200 200" width="100%" height="100%">'+
'<defs>'+
'<clipPath id="dialPath">'+
'<circle cx="100" cy="100" r="100"/>'+
'</clipPath>'+
'</defs>'+
'<filter id="handShadow" color-interpolation-filters="sRGB">'+
'<feFlood result="flood" flood-color="#000" flood-opacity=".4"/>'+
'<feComposite result="composite1" operator="in" in2="SourceGraphic" in="flood"/>'+
'<feGaussianBlur result="blur" stdDeviation="0.5" in="composite1"/>'+
'<feOffset result="offset" dy="0" dx="0"/>'+
'<feComposite result="composite2" operator="atop" in2="offset" in="offset"/>'+
'</filter>'+
'</svg>';
dial.innerHTML = face;
for (var i = 0; i < 60; i++) {
var mrkrLength = (i % 5) ? 7.5 : 25;
var mrkrWidth = (i % 5) ? 3 : 7.5;
mrkrs[i] = document.createElementNS("http://www.w3.org/2000/svg", 'line');
with(mrkrs[i]) {
setAttribute('x1', '100');
setAttribute('y1', '0');
setAttribute('x2', '100');
setAttribute('y2', mrkrLength);
setAttribute('stroke', markColour);
setAttribute('stroke-width', mrkrWidth);
setAttribute('stroke-linecap', 'butt');
setAttribute("clip-path", "url(#dialPath)");
setAttribute( "transform","rotate("+i * 6+", 100, 100)");
}
MobaTimeClock.appendChild(mrkrs[i]);
}
var logo = '<svg width="100%" height="100%" viewBox="0 0 160 106" ' +
'xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">' +
'<path d="M72.764.744C74.936.347 77.136 0 79.35.052c.94.017 1.88.01 2.82.08 4.8' +
'35.364 9.615 1.48 14.098 3.357 4.685 1.99 9.022 4.85 12.635 8.467 3.488 3.46 6' +
'.303 7.605 8.27 12.123 1.837 4.26 2.964 8.834 3.284 13.47.3 4.69-.077 9.427-1.' +
'208 13.988-.398 1.737-.95 3.43-1.494 5.126-3.685-2.417-7.306-4.932-10.977-7.37' +
'.638-2.657 1.03-5.388.955-8.128-.032-2.894-.504-5.78-1.35-8.545-1.36-4.383-3.75' +
'-8.47-7.038-11.658-3.758-3.765-8.715-6.24-13.914-7.168-2.006-.367-4.047-.537-6' +
'.083-.51-2.38.167-4.755.56-7.04 1.256-.07.018-.212.05-.282.067-4.875 1.52-9.37' +
' 4.36-12.692 8.292-3.532 4.122-5.726 9.365-6.367 14.77-.3 2.187-.26 4.41-.16 6' +
'.61.152 1.61.355 3.218.707 4.797-3.644 2.364-7.266 4.76-10.93 7.09-1.812-4.79-' +
'2.977-9.862-3.083-15.002-.237-8.587 2.43-17.226 7.466-24.152C51.114 11.27 56.7' +
'94 6.69 63.212 3.79c3.06-1.366 6.266-2.402 9.552-3.046z" fill-opacity=".9" fill="'+markColour+'"/>' +
'<path d="M48.772 76.595L55.83 71.9l1.774 1.816c1.023 1.047 2.286 2.145 2.984 2' +
'.595 5.233 3.37 12.434 5.067 20.513 4.835 3.703-.106 5.01-.245 8.036-.856 2.72' +
'-.55 5.2-1.387 7.582-2.557 2.47-1.213 4.22-2.505 5.976-4.41l1.357-1.47 7.045 4' +
'.715 7.045 4.715 11.09.06c6.934.038 11.03.002 10.926-.096-.828-.782-35.83-26.7' +
'53-35.916-26.648-4.85 5.9-8.918 9.252-13.73 11.313-3.472 1.487-6.387 2.055-10.' +
'566 2.058-9.514.007-16.507-3.84-24.292-13.368-.08-.096-31.817 23.46-35.66 26.4' +
'67-.395.308-.217.312 10.66.266l11.064-.047 7.056-4.695z" fill="url(#a)"/>' +
'<path d="M81.42 90.82h-3.505l-5.58 15.26h3.473l1.078-2.32 5.597.02 1.08 2.302h' +
'3.32l-5.462-15.26zm-1.717 4.213l1.667 5.944h-3.285l1.618-5.944zm-22.434-4.23h-' +
'5.174v15.26l6.303.016c1.6.02 3.624-1.607 3.994-3.73.084-2.25-.845-4.16-2.376-4' +
'.446.876-.41 1.45-1.89 1.348-3.854-.238-1.944-2.26-3.392-4.097-3.248m.976 3.73' +
'c.353.662.153 1.337-.05 1.837-.656.768-1.837.714-2.78.714v-3.302c.842 0 2.308-' +
'.16 2.83.75m.625 5.747c.37.606.286 1.73.103 2.16-.893.998-2.16.962-3.523.8v-3.' +
'604c1.618-.108 2.46-.09 3.42.643M15.118 90.8l2.41 15.24-3 .037-1.432-8.89-3.75' +
'8 8.89-.926-.037-3.675-8.496-1.517 8.532L0 106.042l2.36-15.277h3.17l3.166 8.744' +
' 3.42-8.708h3zm19.23 11.673c-2.29 0-4.18-2-4.18-4.427 0-2.427 1.89-4.425 4.18-' +
'4.425 2.293 0 4.18 1.998 4.18 4.425 0 2.426-1.887 4.427-4.18 4.427m0 3.604c4.2' +
'8 0 7.77-3.605 7.77-8.032 0-4.425-3.49-8.048-7.77-8.048-4.28 0-7.768 3.623-7.7' +
'68 8.048 0 4.427 3.488 8.03 7.768 8.03M98.68 91.482h8.56l-.285 1.68h-3.49l-1.8' +
'87 12.883h-1.754l1.924-12.884h-3.374l.306-1.68zm18.623 0h1.753l-2.19 14.563h-1' +
'.738l2.175-14.562zm24.506-1.017l.756 15.58h-1.99l-.115-9.94-5.898 10.242-2.715' +
'-10.242-3.166 9.94h-1.972l5.444-15.58 3 11.796 6.657-11.797zm18.236 1.018l-.43' +
'8 1.68h-5.648l-.608 4.086h5.546l-.32 1.66h-5.494l-.876 5.46h5.798l-.286 1.677h' +
'-7.55l2.174-14.562h7.702z" fill-opacity="1.0" fill="'+markColour+'"/>' +
'<defs>' +
'<pattern xlink:href="#b" id="a" patternTransform="translate(364.52 610.068) scale(6.07904)"/>' +
'<pattern id="b" patternTransform="scale(10)" height="1" width="1.07" patternUnits="userSpaceOnUse">' +
'<path fill="'+markColour+'" d="M0 0h.5v1H0z"/>' +
'</pattern>' +
'</defs>' +
'</svg>';
var mtl = d.createElement('div');
mtl.setAttribute('style', 'display: block;'
+'position: absolute;'
+'height: '+xy(9)+'px;'
+'top: '+xy(68)+'px;'
+'paddingBottom: '+xy(1)+'px;'
+'margin: auto; left: 0;right: 0;');
mtl.innerHTML = logo;
dial.appendChild(mtl);
/* Generic container for all hands */
var handContainers = 'display: block;'
+'position: absolute;'
+'height: '+xy(100)+'px;'
+'width: '+xy(100)+'px;'
+'font-size: 0px; line-height: 0px; padding: 0;'
+'margin: auto; top: 0;bottom: 0; left: 0; right: 0;'
+'transform-origin: center center;'
/* Hour hand */
var houClone = handContainers;
var houContainer = d.createElement('div');
houContainer.setAttribute('style', houClone);
houContainer.style.zIndex = 50;
dial.appendChild(houContainer);
var houHand = d.createElement('div');
var housvg = vb +
'<polygon points="95,33 105,33 106,125 94,125" fill="'+handColour+'" stroke="none" "'+dum+'" />'+
'</svg>';
houHand.innerHTML = housvg;
houContainer.appendChild(houHand);
var houShad = houContainer.cloneNode(true);
dial.appendChild(houShad);
genShadKillClone(houShad,housvg, 0, 3);
/* Minute hand */
var minClone = handContainers;
var minContainer = d.createElement('div');
minContainer.setAttribute('style',minClone);
minContainer.style.zIndex = 52;
dial.appendChild(minContainer);
var minHand = d.createElement('div');
var minsvg = vb +
'<polygon points="96,5 104,5 105,125 95,125" fill="'+handColour+'" stroke="none" "'+dum+'" />'+
'</svg>';
minHand.innerHTML = minsvg;
minContainer.appendChild(minHand);
var minShad = minContainer.cloneNode(true);
dial.appendChild(minShad);
genShadKillClone(minShad,minsvg, 0, 4);
/* Seconds hand */
var secClone = handContainers;
var secContainer = d.createElement('div');
secContainer.setAttribute('style',secClone);
secContainer.style.zIndex = 54;
dial.appendChild(secContainer);
var secHand = d.createElement('div');
var secsvg = vb +
'<path d="M100 23.475a10 10 0 0 0-10 10 10 10 0 0 0 8.22 9.832V135h3.56V43.31a10 10 0 0 0 8.22-9.835 10 10 0 0 0-10-10z" fill="'+sechandColour+'" "'+dum+'" />'+
'</svg>';
secHand.innerHTML = secsvg;
secContainer.appendChild(secHand);
var secShad = secContainer.cloneNode(true);
dial.appendChild(secShad);
genShadKillClone(secShad,secsvg, 0, 5);
/* Clock glass */
var glass = d.createElement('div');
glass.setAttribute('style', 'display: block;'
+'position: absolute;'
+'height: '+xy(112)+'px;'
+'width: '+xy(112)+'px;'
+'margin: auto; top: 0; bottom: 0; left: 0;right: 0;'
+'border-radius: '+clockShape+'%;'
+'background-image: url("http://i61.tinypic.com/300xjcn.png");'
+'background-size: cover;'
+'opacity: 0.3;'
+'z-index: 55;'
+'overflow: hidden;');
innerRim.appendChild(glass);
function secKeyFrames() {
var secSheet = (d.getElementById('tmpSecSheet'+idx));
if (secSheet) {
secSheet.parentNode.removeChild(secSheet);
}
secClone = handContainers;
var p1 = secDeg;
var p2 = secDeg+6;
var p3 = secDeg+4;
var p4 = secDeg+6;
var p5 = secDeg+5;
var p6 = secDeg+6;
var secframes = '#keyframes s'+idx+'gen'+secIncr+' { '
+'0% { transform: rotate('+p1+'deg) '+eiatf+';}'
+'30% { transform: rotate('+p2+'deg) '+eoatf+';}'
+'45% { transform: rotate('+p3+'deg) '+eiatf+';}'
+'60% { transform: rotate('+p4+'deg) '+eoatf+';}'
+'70% { transform: rotate('+p5+'deg) '+eiatf+';}'
+'80%,100% { transform: rotate('+p6+'deg) '+eoatf+';}}';
var ss = document.createElement( 'style' );
ss.setAttribute('id', 'tmpSecSheet'+idx);
ss.innerHTML = secframes;
document.getElementsByTagName('head')[0].appendChild(ss);
var secAni = 'animation: s'+idx+'gen'+secIncr+' '+secSpan+' 1 forwards;';
secClone += secAni;
secHand.setAttribute('style', secClone);
secHand.style.zIndex = 104;
dial.appendChild(secHand);
secShad.setAttribute('style', secClone);
secShad.style.top = xy(5)+'px';
secShad.style.left = xy(0)+'px';
}
function minKeyFrames() {
var minSheet = (d.getElementById('tmpMinSheet'+idx));
if (minSheet) {
minSheet.parentNode.removeChild(minSheet);
}
minClone = handContainers;
var p1 = minDeg;
var p2 = minDeg+6;
var p3 = minDeg+4;
var p4 = minDeg+6;
var p5 = minDeg+5;
var p6 = minDeg+6;
var minframes = '#keyframes m'+idx+'gen'+minIncr+' { '
+'0% { transform: rotate('+p1+'deg) '+eiatf+';}'
+'30% { transform: rotate('+p2+'deg) '+eoatf+';}'
+'45% { transform: rotate('+p3+'deg) '+eiatf+';}'
+'60% { transform: rotate('+p4+'deg) '+eoatf+';}'
+'70% { transform: rotate('+p5+'deg) '+eiatf+';}'
+'80%,100% { transform: rotate('+p6+'deg) '+eoatf+';}}';
var ms = document.createElement( 'style' );
ms.setAttribute('id', 'tmpMinSheet'+idx);
ms.innerHTML = minframes;
d.getElementsByTagName('head')[0].appendChild(ms);
var minAni = 'animation: m'+idx+'gen'+minIncr+' '+minSpan+' 1 forwards;';
minClone += minAni;
minHand.setAttribute('style', minClone);
minHand.style.zIndex = 102;
dial.appendChild(minHand);
minShad.setAttribute('style', minClone);
minShad.style.top = xy(4)+'px';
minShad.style.left = xy(0)+'px';
}
function houKeyFrames() {
var houSheet = (d.getElementById('tmphouSheet'+idx));
if (houSheet) {
houSheet.parentNode.removeChild(houSheet);
}
houClone = handContainers;
var p1 = houDeg;
var p2 = houDeg+1;
var p3 = houDeg+0.4;
var p4 = houDeg+1;
var p5 = houDeg+0.5;
var p6 = houDeg+1;
var houframes = '#keyframes h'+idx+'gen'+houIncr+' { '
+'0% { transform: rotate('+p1+'deg) '+eiatf+';}'
+'30% { transform: rotate('+p2+'deg) '+eoatf+';}'
+'45% { transform: rotate('+p3+'deg) '+eiatf+';}'
+'60% { transform: rotate('+p4+'deg) '+eoatf+';}'
+'70% { transform: rotate('+p5+'deg) '+eiatf+';}'
+'80%,100% { transform: rotate('+p6+'deg) '+eoatf+';}}';
var hs = document.createElement( 'style' );
hs.setAttribute('id', 'tmphouSheet'+idx);
hs.innerHTML = houframes;
d.getElementsByTagName('head')[0].appendChild(hs);
var houAni = 'animation: h'+idx+'gen'+houIncr+' '+houSpan+' 1 forwards;';
houClone += houAni;
houHand.setAttribute('style', houClone);
houHand.style.zIndex = 100;
dial.appendChild(houHand);
houShad.setAttribute('style', houClone);
houShad.style.top = xy(3)+'px';
houShad.style.left = xy(0)+'px';
}
function mobaSeconds() {
mobasec++;
secIncr++;
secDeg = (mobasec-1) * 6;
secHand.removeAttribute('style');
secKeyFrames();
if (secIncr > 59) {
secIncr = 0;
}
mobaTimer = setTimeout(mobaSeconds, mobacycle);
if (mobasec > 59) {
clearTimeout(mobaTimer);
mobasec = 0;
firstrun = false;
}
}
function clock() {
var x = new Date();
var seconds = x.getSeconds();
var minutes = x.getMinutes();
var hours = (x.getHours() * 30) + (x.getMinutes() / 2);
if (seconds !== preSec) {
mobacycle -= mobaoffset;
if (!firstrun && seconds == 1) {
mobaSeconds();
}
}
if (minutes !== preMin) {
if (firstrun) {
mobaSeconds();
}
if (!firstrun) {
mobacycle = 1000-mobaoffset;
}
minIncr++;
minDeg = (minutes-1) * 6;
minHand.removeAttribute('style');
minKeyFrames();
if (minIncr > 59) {
minIncr = 0;
}
}
if (hours !== preHou) {
houIncr++;
houDeg = (hours-1) * 1;
houHand.removeAttribute('style');
houKeyFrames();
if (houIncr > 59) {
houIncr = 0;
}
}
preSec = seconds;
preMin = minutes;
preHou = hours;
tmr = setTimeout(clock, mls);
}
window.addEventListener('load', clock, false);
})();
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Hilfiker/MobaTime Swiss Railway Clock</title>
<style type="text/css">
body {
background-color: rgb(205,179,139);
text-align: center;
}
</style>
</head>
<body>
</body>
</html>

Zoom on selected svg element using javascript

i am working on an application which require Zoom In, Zoom out and Panning. I have achieved all these functionality using viewBox property of svg.
My current Zoom In works fine but it zoom toward the center of screen. I want to add additional functionality of Zoom in toward a selected element. I know i can set viewBox to the selected element bbox values, but i want sequential/smooth zoom in which does not conflict with my current/default zoom in.
how i can achieve this?
here is jsfiddle for the sample code:-
http://jsfiddle.net/55G9c/
HTML Code
<div onclick="zoomin()" style="display: block;float: left;border: 1px solid;cursor: pointer">
ZoomIn
</div>
<div onclick="zoomout()" style="display: block;float: left;border: 1px solid;cursor: pointer;margin-left: 10px">
ZoomOut
</div>
<svg id="mainsvg" width="600px" height="500px" viewBox="0 0 600 500">
<g id="gnode">
<rect id="boundry" x="0" y="0" width="599" height="499" fill="none" stroke='black'/>
<circle id="centernode" cx="300" cy="250" r="5" fill="red" stroke="none" />
<rect id="selected" x="450" y="100" width="50" height="50" fill="blue" stroke='none'/>
</g>
</svg>
Javascript Code
var svg=document.getElementById('mainsvg');
var gnode=document.getElementById('gnode');
var zoomPercentage=0.25;
var MAXIMUM_ZOOM_HEIGHT = 1400;
var baseBox={};
var level=0;
var widthRatio,heightRatio;
var clientheight = document.documentElement.clientHeight;
var clientwidth = document.documentElement.clientWidth;
function setup(){
var
baseX,
baseY,
baseWidth,
baseHeight,
percentageDifference,
heightDifference;
svg.setAttribute('height', clientheight);
svg.setAttribute('width', clientwidth);
var boundry=document.getElementById('boundry');
boundry.setAttribute('height', clientheight-1);
boundry.setAttribute('width', clientwidth-1);
var centernode=document.getElementById('centernode');
centernode.setAttribute('cy', clientheight/2);
centernode.setAttribute('cx', clientwidth/2);
if (svg.height.baseVal.value >= MAXIMUM_ZOOM_HEIGHT)
baseHeight = MAXIMUM_ZOOM_HEIGHT;
else
baseHeight = Math.round(gnode.getBBox().height) + 60;
baseY = (svg.height.baseVal.value - baseHeight) / 2;
percentageDifference = baseHeight / svg.height.baseVal.value;
baseWidth = percentageDifference * svg.width.baseVal.value;
baseX = (svg.width.baseVal.value - baseWidth) / 2;
baseBox.x = baseX;
baseBox.y = baseY;
baseBox.width = baseWidth;
baseBox.height = baseHeight;
level = 0;
heightDifference = MAXIMUM_ZOOM_HEIGHT - baseHeight;
zoomPercentage = (heightDifference / 10) / heightDifference;
setViewBox(baseBox);
}
function setViewBox(viewBox) {
svg.viewBox.baseVal.x = Math.round(viewBox.x);
svg.viewBox.baseVal.y = Math.round(viewBox.y);
svg.viewBox.baseVal.width = Math.round(viewBox.width);
svg.viewBox.baseVal.height = Math.round(viewBox.height);
setRatios();
}
function setRatios () {
widthRatio = svg.viewBox.baseVal.width / svg.width.baseVal.value;
heightRatio = svg.viewBox.baseVal.height / svg.height.baseVal.value;
}
function calculateViewBox(level) {
var
height = baseBox.height - (zoomPercentage * level * baseBox.height),
y = baseBox.y + (baseBox.height - height) / 2,
width = baseBox.width - (zoomPercentage * level * baseBox.width),
x = baseBox.x + (baseBox.width - width) / 2,
viewBox = {
x: x,
y: y,
width: width,
height: height
}
return viewBox;
}
function zoomin(){
level++;
if(level>5)
level=5;
var
x,
y,
paperViewBox = svg.viewBox.baseVal,
previousViewBox = calculateViewBox(level - 1),
newViewBox = calculateViewBox(level);
//callback = this.afterZoom;
if (Math.round(paperViewBox.x) > Math.round(newViewBox.x))
/**
* is panned left
*/
x = paperViewBox.x - (previousViewBox.width - newViewBox.width) / 2;
else if (Math.round(paperViewBox.x) < Math.round(previousViewBox.x) - (Math.round(newViewBox.x) - Math.round(previousViewBox.x)))
/**
* is panned right
*/
x = paperViewBox.x + (previousViewBox.width - newViewBox.width) + (previousViewBox.width - newViewBox.width) / 2;
else
x = newViewBox.x;
if (Math.round(paperViewBox.y) > Math.round(newViewBox.y))
/**
* is panned up
*/
y = paperViewBox.y - (previousViewBox.height - newViewBox.height) / 2;
else if (Math.round(paperViewBox.y) < Math.round(previousViewBox.y) - (Math.round(newViewBox.y) - Math.round(previousViewBox.y)))
/**
* is panned down
*/
y = paperViewBox.y + (previousViewBox.height - newViewBox.height) + (previousViewBox.height - newViewBox.height) / 2;
else
y = newViewBox.y;
var data = {
viewBox: {
x: x,
y: y,
width: newViewBox.width,
height: newViewBox.height
}
}
SetZoomViewBox(data);
}
function SetZoomViewBox(data){
var viewBox = data.viewBox;
svg.viewBox.baseVal.x = Math.round(viewBox.x);
svg.viewBox.baseVal.y = Math.round(viewBox.y);
svg.viewBox.baseVal.width = Math.round(viewBox.width);
svg.viewBox.baseVal.height = Math.round(viewBox.height);
setRatios();
}
function zoomout(){
level--;
if(level<0)
level=0;
var
x,
y,
paperViewBox = svg.viewBox.baseVal,
previousViewBox = calculateViewBox(level + 1),
newViewBox = calculateViewBox(level);
if (Math.round(paperViewBox.x) > Math.round(previousViewBox.x) + (Math.round(previousViewBox.x) - Math.round(newViewBox.x)))
/**
* is panned left
*/
x = paperViewBox.x - (newViewBox.width - previousViewBox.width);
else if (Math.round(paperViewBox.x) < Math.round(previousViewBox.x))
/**
* is panned right
*/
x = paperViewBox.x;
else
x = newViewBox.x;
if (Math.round(paperViewBox.y) > Math.round(previousViewBox.y) + (Math.round(previousViewBox.y) - Math.round(newViewBox.y)))
/**
* is panned up
*/
y = paperViewBox.y - (newViewBox.height - previousViewBox.height);
else if (Math.round(paperViewBox.y) < Math.round(previousViewBox.y))
/**
* is panned down
*/
y = paperViewBox.y;
else
y = newViewBox.y;
var data = {
viewBox: {
x: x,
y: y,
width: newViewBox.width,
height: newViewBox.height
}
}
SetZoomViewBox(data);
}
setup();
Here's an example that shows an SVG file with a zoom/pan control.
It sets the currentScale and currentTranslate attributes on the SVG root element to perform zooming/panning.
try running on firefox. click the center green circle to zoom out and red circle to zoom in. Probably you'll get some idea.

Categories