Interactive Doughnut Web Chart - javascript

I have a chart that i'm trying to make more user friendly and I keep running into issues trying to add one thing, something else goes wrong.
what im trying to accomplish is: Chart starts with one in 'hovered' state. When one is hovered on, it has a border as well as the text in the middle of the chart updates with the label and percentage. I would also like the background to be a SVG image (not important if not possible) .
I've made a fiddle - I would like to remove the tooltip too.
Any help would be greatly appreciated!
IMG of what im trying to accomplish:
my HTML code:
<div id="doughnutChart" class="chart"></div>
jQuery
jQuery(function(){
jQuery("#doughnutChart").drawDoughnutChart([
{ title: "Holiday Fund", value : 5, color: "#2C3E50" },
{ title: "Emergencies", value: 20, color: "#FC4349" },
{ title: "Loans", value: 20, color: "#6DBCDB" },
{ title: "Widows", value : 27, color: "#F7E248" },
{ title: "Medical Support", value : 28, color: "#D7DADB" },
]);
});
/*!
* jquery.drawDoughnutChart.js
* Version: 0.4.1(Beta)
* Inspired by Chart.js(http://www.chartjs.org/)
*
* Copyright 2014 hiro
* https://github.com/githiro/drawDoughnutChart
* Released under the MIT license.
*
*/
;(function($, undefined) {
$.fn.drawDoughnutChart = function(data, options) {
var $this = this,
W = $this.width(),
H = $this.height(),
centerX = W/2,
centerY = H/2,
cos = Math.cos,
sin = Math.sin,
PI = Math.PI,
settings = $.extend({
segmentShowStroke : true,
segmentStrokeColor : "#0C1013",
segmentStrokeWidth : 1,
baseColor: "rgba(0,0,0,0.5)",
baseOffset: 4,
edgeOffset : 10,//offset from edge of $this
percentageInnerCutout : 75,
animation : true,
animationSteps : 90,
animationEasing : "easeInOutExpo",
animateRotate : true,
tipOffsetX: -8,
tipOffsetY: -45,
tipClass: "doughnutTip",
summaryClass: "doughnutSummary",
summaryTitle: "",
summaryTitleClass: "doughnutSummaryTitle",
summaryNumberClass: "doughnutSummaryNumber",
beforeDraw: function() { },
afterDrawed : function() { },
onPathEnter : function(e,data) { },
onPathLeave : function(e,data) { }
}, options),
animationOptions = {
linear : function (t) {
return t;
},
easeInOutExpo: function (t) {
var v = t<.5 ? 8*t*t*t*t : 1-8*(--t)*t*t*t;
return (v>1) ? 1 : v;
}
},
requestAnimFrame = function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
}();
settings.beforeDraw.call($this);
var $svg = $('<svg width="' + W + '" height="' + H + '" viewBox="0 0 ' + W + ' ' + H + '" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"></svg>').appendTo($this),
$paths = [],
easingFunction = animationOptions[settings.animationEasing],
doughnutRadius = Min([H / 2,W / 2]) - settings.edgeOffset,
cutoutRadius = doughnutRadius * (settings.percentageInnerCutout / 100),
segmentTotal = 0;
//Draw base doughnut
var baseDoughnutRadius = doughnutRadius + settings.baseOffset,
baseCutoutRadius = cutoutRadius - settings.baseOffset;
$(document.createElementNS('http://www.w3.org/2000/svg', 'path'))
.attr({
"d": getHollowCirclePath(baseDoughnutRadius, baseCutoutRadius),
"fill": settings.baseColor
})
.appendTo($svg);
//Set up pie segments wrapper
var $pathGroup = $(document.createElementNS('http://www.w3.org/2000/svg', 'g'));
$pathGroup.attr({opacity: 0}).appendTo($svg);
//Set up tooltip
var $tip = $('<div class="' + settings.tipClass + '" />').appendTo('body').hide(),
tipW = $tip.width(),
tipH = $tip.height();
for (var i = 0, len = data.length; i < len; i++) {
segmentTotal += data[i].value;
$paths[i] = $(document.createElementNS('http://www.w3.org/2000/svg', 'path'))
.attr({
"stroke-width": settings.segmentStrokeWidth,
"stroke": settings.segmentStrokeColor,
"fill": data[i].color,
"data-order": i,
"class": 'counter-'+i
})
.appendTo($pathGroup)
.on("mouseenter", pathMouseEnter)
.on("mouseleave", pathMouseLeave)
.on("mousemove", pathMouseMove);
}
//Set up center text area
var summarySize = (cutoutRadius - (doughnutRadius - cutoutRadius)) * 2,
$summary = $('<div class="' + settings.summaryClass + '" />')
.appendTo($this)
.css({
width: summarySize + "px",
height: summarySize + "px",
"margin-left": -(summarySize / 2) + "px",
"margin-top": -(summarySize / 2) + "px"
});
var $summaryTitle = $('<p class="' + settings.summaryTitleClass + '">' + data[0].title + "<br />" + data[0].value + '%' + '</p>').appendTo($summary);
//var $summaryNumber = $('<p class="' + settings.summaryNumberClass + '"></p>').appendTo($summary).css({opacity: 0});
//Animation start
animationLoop(drawPieSegments);
//Functions
function getHollowCirclePath(doughnutRadius, cutoutRadius) {
//Calculate values for the path.
//We needn't calculate startRadius, segmentAngle and endRadius, because base doughnut doesn't animate.
var startRadius = -1.570,// -Math.PI/2
segmentAngle = 6.2831,// 1 * ((99.9999/100) * (PI*2)),
endRadius = 4.7131,// startRadius + segmentAngle
startX = centerX + cos(startRadius) * doughnutRadius,
startY = centerY + sin(startRadius) * doughnutRadius,
endX2 = centerX + cos(startRadius) * cutoutRadius,
endY2 = centerY + sin(startRadius) * cutoutRadius,
endX = centerX + cos(endRadius) * doughnutRadius,
endY = centerY + sin(endRadius) * doughnutRadius,
startX2 = centerX + cos(endRadius) * cutoutRadius,
startY2 = centerY + sin(endRadius) * cutoutRadius;
var cmd = [
'M', startX, startY,
'A', doughnutRadius, doughnutRadius, 0, 1, 1, endX, endY,//Draw outer circle
'Z',//Close path
'M', startX2, startY2,//Move pointer
'A', cutoutRadius, cutoutRadius, 0, 1, 0, endX2, endY2,//Draw inner circle
'Z'
];
cmd = cmd.join(' ');
return cmd;
};
function pathMouseEnter(e) {
var order = $(this).data().order;
$tip.text(data[order].title + ": " + data[order].value)
.fadeIn(200);
settings.onPathEnter.apply($(this),[e,data]);
$('.doughnutSummaryTitle').html(data[order].title + "<br />" + data[order].value + '%')
}
function pathMouseLeave(e) {
$tip.hide();
settings.onPathLeave.apply($(this),[e,data]);
}
function pathMouseMove(e) {
$tip.css({
top: e.pageY + settings.tipOffsetY,
left: e.pageX - $tip.width() / 2 + settings.tipOffsetX
});
}
function drawPieSegments (animationDecimal) {
var startRadius = -PI / 2,//-90 degree
rotateAnimation = 1;
if (settings.animation && settings.animateRotate) rotateAnimation = animationDecimal;//count up between0~1
//drawDoughnutText(animationDecimal, segmentTotal);
$pathGroup.attr("opacity", animationDecimal);
//If data have only one value, we draw hollow circle(#1).
if (data.length === 1 && (4.7122 < (rotateAnimation * ((data[0].value / segmentTotal) * (PI * 2)) + startRadius))) {
$paths[0].attr("d", getHollowCirclePath(doughnutRadius, cutoutRadius));
return;
}
for (var i = 0, len = data.length; i < len; i++) {
var segmentAngle = rotateAnimation * ((data[i].value / segmentTotal) * (PI * 2)),
endRadius = startRadius + segmentAngle,
largeArc = ((endRadius - startRadius) % (PI * 2)) > PI ? 1 : 0,
startX = centerX + cos(startRadius) * doughnutRadius,
startY = centerY + sin(startRadius) * doughnutRadius,
endX2 = centerX + cos(startRadius) * cutoutRadius,
endY2 = centerY + sin(startRadius) * cutoutRadius,
endX = centerX + cos(endRadius) * doughnutRadius,
endY = centerY + sin(endRadius) * doughnutRadius,
startX2 = centerX + cos(endRadius) * cutoutRadius,
startY2 = centerY + sin(endRadius) * cutoutRadius;
var cmd = [
'M', startX, startY,//Move pointer
'A', doughnutRadius, doughnutRadius, 0, largeArc, 1, endX, endY,//Draw outer arc path
'L', startX2, startY2,//Draw line path(this line connects outer and innner arc paths)
'A', cutoutRadius, cutoutRadius, 0, largeArc, 0, endX2, endY2,//Draw inner arc path
'Z'//Cloth path
];
$paths[i].attr("d", cmd.join(' '));
startRadius += segmentAngle;
}
}
//function drawDoughnutText(animationDecimal, segmentTotal) {
// $summaryNumber
// .css({opacity: animationDecimal})
// .text((segmentTotal * animationDecimal).toFixed(1));
//}
function animateFrame(cnt, drawData) {
var easeAdjustedAnimationPercent =(settings.animation)? CapValue(easingFunction(cnt), null, 0) : 1;
drawData(easeAdjustedAnimationPercent);
}
function animationLoop(drawData) {
var animFrameAmount = (settings.animation)? 1 / CapValue(settings.animationSteps, Number.MAX_VALUE, 1) : 1,
cnt =(settings.animation)? 0 : 1;
requestAnimFrame(function() {
cnt += animFrameAmount;
animateFrame(cnt, drawData);
if (cnt <= 1) {
requestAnimFrame(arguments.callee);
} else {
settings.afterDrawed.call($this);
}
});
}
function Max(arr) {
return Math.max.apply(null, arr);
}
function Min(arr) {
return Math.min.apply(null, arr);
}
function isNumber(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
function CapValue(valueToCap, maxValue, minValue) {
if (isNumber(maxValue) && valueToCap > maxValue) return maxValue;
if (isNumber(minValue) && valueToCap < minValue) return minValue;
return valueToCap;
}
return $this;
};
})(jQuery);
CSS
.chart {
position: absolute;
width: 450px;
height: 450px;
top: 50%;
left: 50%;
margin: -225px 0 0 -225px;
}
.doughnutTip {
position: absolute;
min-width: 30px;
max-width: 300px;
padding: 5px 15px;
border-radius: 1px;
background: rgba(0,0,0,.8);
color: #ddd;
font-size: 17px;
text-shadow: 0 1px 0 #000;
text-transform: uppercase;
text-align: center;
line-height: 1.3;
letter-spacing: .06em;
box-shadow: 0 1px 3px rgba(0,0,0,0.5);
pointer-events: none;
&::after {
position: absolute;
left: 50%;
bottom: -6px;
content: "";
height: 0;
margin: 0 0 0 -6px;
border-right: 5px solid transparent;
border-left: 5px solid transparent;
border-top: 6px solid rgba(0,0,0,.7);
line-height: 0;
}
}
.doughnutSummary {
position: absolute;
top: 50%;
left: 50%;
color: #000;
text-align: center;
text-shadow: 0 -1px 0 #111;
cursor: default;
}
.doughnutSummaryTitle {
position: absolute;
top: 50%;
width: 100%;
margin-top: -27%;
font-size: 22px;
letter-spacing: .06em;
}
.doughnutSummaryNumber {
position: absolute;
top: 50%;
width: 100%;
margin-top: -15%;
font-size: 55px;
}
.chart path:hover { opacity: 0.65; }
JSFIDDLE

Without modifying the original plugin drawDoughnutChart, you could overwrite the default settings.
For the out stroke you could draw an other chart, hide it, and use afterDrawed, onPathEnter, onPathLeave callbacks exposed by the plugin to do the logic.
And for the background of each pie segment, you could use svg pattern.
Javascript:
var firstSelected = 0;
var seed = [
{ title: "Holiday Fund", value : 5, color: "url(#dots2)" },
{ title: "Emergencies", value: 20, color: "url(#diagonal1)" },
{ title: "Loans", value: 20, color: "url(#dots1)" },
{ title: "Widows", value : 27, color: "url(#diagonal2)" },
{ title: "Medical Support", value : 28, color: "url(#hatch1)" },
];
var seed2 = [
{ value : 5, color: "#9fa1ac" },
{ value: 20, color: "#ef5123" },
{ value: 20, color: "#ef5123" },
{ value : 27, color: "#9fa1ac" },
{ value : 28, color: "#ef5123" },
];
var chartOptions = {
baseOffset: 0,
segmentShowStroke : false,
segmentStrokeColor : 'transparent',
baseColor: 'transparent',
percentageInnerCutout : 60,
onPathEnter: function (e, data) {
var order = $(this).data().order;
$('#doughnutChart .doughnutSummaryTitle').html(data[order].title);
$('#doughnutChart .doughnutSummaryNumber').html(data[order].value + '%');
$('#doughnutChart .doughnutSummary').show();
$('#doughnutBg g').find('path').fadeOut(300);
$('#doughnutBg g').find('path:eq('+(order)+')').fadeIn(500)
},
onPathLeave: function (e, data) {
$('#doughnutBg g').find('path').fadeOut(300);
$('#doughnutChart .doughnutSummary').hide();
},
afterDrawed : function () {
$('#doughnutChart .doughnutSummaryTitle').html(seed[firstSelected].title);
$('#doughnutChart .doughnutSummaryNumber').html(seed[firstSelected].value + '%');
$('#doughnutChart .doughnutSummary').css({width: '160px', height: '60px', marginLeft: '-80px', marginTop: '-30px'});
$('#doughnutChart .doughnutSummary').show();
$('#doughnutBg g').find('path:eq('+firstSelected+')').fadeIn(500);
}
} ;
var chartOptions2 = {
baseOffset: 0,
segmentStrokeColor : 'transparent',
segmentShowStroke : false,
percentageInnerCutout : 95
} ;
jQuery("#doughnutChart").drawDoughnutChart(seed, chartOptions);
jQuery("#doughnutBg").drawDoughnutChart(seed2, chartOptions2);
CSS:
.chart {
position: absolute;
width: 400px;
height: 400px;
top: 50%;
left: 50%;
margin: -200px 0 0 -200px;
}
.bgchart {
position: absolute;
width: 430px;
height: 430px;
top: 50%;
left: 50%;
margin: -215px 0 0 -215px;
}
.chart .doughnutSummary,
.bgchart .doughnutSummary{
display: none;
}
.chart path:hover { opacity: 0.65; }
.bgchart path { display: none }
HTML:
<svg width="8cm" height="4cm" viewBox="0 0 800 400" version="1.1"
xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="hatch1" patternUnits="userSpaceOnUse" x="0" y="0" width="10" height="10">
<g style="fill:none; stroke:#ef5123; stroke-width:1">
<path d="M0,0 l10,10"/><path d="M10,0 l-10,10"/>
</g>
</pattern>
<pattern id="hatch2" patternUnits="userSpaceOnUse" x="0" y="0" width="10" height="10">
<g style="fill:none; stroke:#9fa1ac; stroke-width:1">
<path d="M0,0 l10,10"/><path d="M10,0 l-10,10"/>
</g>
</pattern>
<pattern id="diagonal1" x="0" y="0" width="6" height="6" patternUnits="userSpaceOnUse" patternTransform="rotate(130)">
<rect x="0" y="0" width="2" height="6" style="stroke:none; fill:#ef5123;" />
</pattern>
<pattern id="diagonal2" x="0" y="0" width="6" height="6" patternUnits="userSpaceOnUse" patternTransform="rotate(130)">
<rect x="0" y="0" width="2" height="6" style="stroke:none; fill:#9fa1ac;" />
</pattern>
<pattern id="dots1" x="0" y="0" width="5" height="5" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
<circle cx="2" cy="2" r="1" style="stroke:none; fill:#ef5123;" />
</pattern>
<pattern id="dots2" x="0" y="0" width="5" height="5" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
<circle cx="2" cy="2" r="1" style="stroke:none; fill:#9fa1ac;" />
</pattern>
</defs>
</svg>
<div class="bgchart" id="doughnutBg"></div>
<div class="chart" id="doughnutChart"></div>
DEMO

Related

Animation optimization in pure js

There is an animation of the flight of leaves in pure js. The problem is that it needs to be optimized for maximum performance, because there will be more animations of this kind on the page. Besides optimizing the original SVG image and reducing the number of leaflets, what tips can you give to improve the performance of your code?
var LeafScene = function(el) {
this.viewport = el;
this.world = document.createElement('div');
this.leaves = [];
this.options = {
numLeaves: 6,
wind: {
magnitude: 0,
maxSpeed: 12,
duration: 300,
start: 0,
speed: 10
},
};
this.width = this.viewport.offsetWidth;
this.height = this.viewport.offsetHeight;
this.timer = 0;
this._resetLeaf = function(leaf) {
leaf.x = this.width * 2 - Math.random()*this.width*1.75;
leaf.y = -10;
leaf.z = Math.random()*200;
if (leaf.x > this.width) {
leaf.x = this.width + 10;
leaf.y = Math.random()*this.height/2;
}
if (this.timer == 0) {
leaf.y = Math.random()*this.height;
}
leaf.rotation.speed = Math.random()*10;
var randomAxis = Math.random();
if (randomAxis > 0.5) {
leaf.rotation.axis = 'X';
} else if (randomAxis > 0.25) {
leaf.rotation.axis = 'Y';
leaf.rotation.x = Math.random()*180 + 90;
} else {
leaf.rotation.axis = 'Z';
leaf.rotation.x = Math.random()*360 - 180;
leaf.rotation.speed = Math.random()*3;
}
leaf.xSpeedVariation = Math.random() * 0.8 - 0.4;
leaf.ySpeed = Math.random() + 1.5;
return leaf;
}
this._updateLeaf = function(leaf) {
var leafWindSpeed = this.options.wind.speed(this.timer - this.options.wind.start, leaf.y);
var xSpeed = leafWindSpeed + leaf.xSpeedVariation;
leaf.x -= xSpeed;
leaf.y += leaf.ySpeed;
leaf.rotation.value += leaf.rotation.speed;
var t = 'translateX( ' + leaf.x + 'px ) translateY( ' + leaf.y + 'px ) translateZ( ' + leaf.z + 'px ) rotate' + leaf.rotation.axis + '( ' + leaf.rotation.value + 'deg )';
if (leaf.rotation.axis !== 'X') {
t += ' rotateX(' + leaf.rotation.x + 'deg)';
}
leaf.el.style.webkitTransform = t;
leaf.el.style.MozTransform = t;
leaf.el.style.oTransform = t;
leaf.el.style.transform = t;
if (leaf.x < -10 || leaf.y > this.height + 10) {
this._resetLeaf(leaf);
}
}
this._updateWind = function() {
if (this.timer === 0 || this.timer > (this.options.wind.start + this.options.wind.duration)) {
this.options.wind.magnitude = Math.random() * this.options.wind.maxSpeed;
this.options.wind.duration = this.options.wind.magnitude * 50 + (Math.random() * 20 - 10);
this.options.wind.start = this.timer;
var screenHeight = this.height;
this.options.wind.speed = function(t, y) {
var a = this.magnitude/2 * (screenHeight - 2*y/3)/screenHeight;
return a * Math.sin(2*Math.PI/this.duration * t + (3 * Math.PI/2)) + a;
}
}
}
}
LeafScene.prototype.init = function() {
for (var i = 0; i < this.options.numLeaves; i++) {
var leaf = {
el: document.createElement('div'),
x: 0,
y: 0,
z: 0,
rotation: {
axis: 'X',
value: 0,
speed: 0,
x: 0
},
xSpeedVariation: 0,
ySpeed: 0,
path: {
type: 1,
start: 0,
},
image: 1
};
this._resetLeaf(leaf);
this.leaves.push(leaf);
this.world.appendChild(leaf.el);
}
this.world.className = 'leaf-scene';
this.viewport.appendChild(this.world);
this.world.style.webkitPerspective = "400px";
this.world.style.MozPerspective = "400px";
this.world.style.oPerspective = "400px";
this.world.style.perspective = "400px";
var self = this;
window.onresize = function(event) {
self.width = self.viewport.offsetWidth;
self.height = self.viewport.offsetHeight;
};
}
LeafScene.prototype.render = function() {
this._updateWind();
for (var i = 0; i < this.leaves.length; i++) {
this._updateLeaf(this.leaves[i]);
}
this.timer++;
requestAnimationFrame(this.render.bind(this));
}
var leafContainer = document.querySelector('.falling-leaves'),
leaves = new LeafScene(leafContainer);
leaves.init();
leaves.render();
body, html {
height: 100%;
}
.falling-leaves {
position: absolute;
top: 0;
bottom: 0;
left: 50%;
width: 100%;
max-width: 880px;
max-height: 880px;
transform: translate(-50%, 0);
border: 20px solid #fff;
border-radius: 50px;
background-size: cover;
overflow: hidden;
}
.leaf-scene {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 100%;
transform-style: preserve-3d;
}
.leaf-scene div {
position: absolute;
top: 0;
left: 0;
width: 20px;
height: 20px;
background: url(https://www.flaticon.com/svg/static/icons/svg/892/892881.svg) no-repeat;
background-size: 100%;
transform-style: preserve-3d;
-webkit-backface-visibility: visible;
backface-visibility: visible;
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">
<div class="falling-leaves"></div>
Unless you're using HTML Canvas, vanilla JS solutions are pretty heavy compared to CSS/JS hybrid because of the browser call stack.
It looks like this:
JS > Style > Layout > Paint > Composite
By minimizing the amount of calculations the browser has to do in JS and grouping reads/writes to the DOM you'll see a significant performance increase. The workload can be recorded with Chrome Dev tools under 'Performance' tab. And you'll be able to see every step the browser is taking to display the content. The less steps, the better performance .. simple as that.
You're writing to the DOM every single transform which is heavy even with 3d acceleration.
I usually make use of CSS variables and transitions for dynamic animation that I update in steps.
What you did is great otherwise. Try rendering on a HTML Canvas and your performance problems will vanish. Here's a start:
https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API
But if you want or cant do canvas adapt to utilize CSS to not drop further than the Paint browser call for majority of frames.

Restricting Javascript based Background Animation to some Particular Div

I have been trying to use an Animated Background, using Javascript I found on this link : https://codepen.io/nashvail/pen/wpGgXO
It's working fine. But the problem is that once implemented, it's all over the screen.
I want the animations to be in some particular DIV element of my web page.
Help me Out.
// Some random colors
const colors = ["#3CC157", "#2AA7FF", "#1B1B1B", "#FCBC0F", "#F85F36"];
const numBalls = 50;
const balls = [];
for (let i = 0; i < numBalls; i++) {
let ball = document.createElement("div");
ball.classList.add("ball");
ball.style.background = colors[Math.floor(Math.random() * colors.length)];
ball.style.left = `${Math.floor(Math.random() * 100)}vw`;
ball.style.top = `${Math.floor(Math.random() * 100)}vh`;
ball.style.transform = `scale(${Math.random()})`;
ball.style.width = `${Math.random()}em`;
ball.style.height = ball.style.width;
balls.push(ball);
document.body.append(ball);
}
// Keyframes
balls.forEach((el, i, ra) => {
let to = {
x: Math.random() * (i % 2 === 0 ? -11 : 11),
y: Math.random() * 12
};
let anim = el.animate(
[
{ transform: "translate(0, 0)" },
{ transform: `translate(${to.x}rem, ${to.y}rem)` }
],
{
duration: (Math.random() + 1) * 2000, // random duration
direction: "alternate",
fill: "both",
iterations: Infinity,
easing: "ease-in-out"
}
);
});
.ball {
position: absolute;
border-radius: 100%;
opacity: 0.7;
}
<div class="ball">
</div>
Like this?
// Some random colors
const colors = ["#3CC157", "#2AA7FF", "#1B1B1B", "#FCBC0F", "#F85F36"];
const numBalls = 50;
const balls = [];
for (let i = 0; i < numBalls; i++) {
let ball = document.createElement("div");
ball.classList.add("ball");
ball.style.background = colors[Math.floor(Math.random() * colors.length)];
ball.style.left = `${Math.floor(Math.random() * 100)}%`;
ball.style.top = `${Math.floor(Math.random() * 100)}%`;
ball.style.transform = `scale(${Math.random()})`;
ball.style.width = `${Math.random()}em`;
ball.style.height = ball.style.width;
balls.push(ball);
document.getElementById("box").append(ball);
}
// Keyframes
balls.forEach((el, i, ra) => {
let to = {
x: Math.random() * (i % 2 === 0 ? -11 : 11),
y: Math.random() * 12
};
let anim = el.animate(
[
{ transform: "translate(0, 0)" },
{ transform: `translate(${to.x}rem, ${to.y}rem)` }
],
{
duration: (Math.random() + 1) * 2000, // random duration
direction: "alternate",
fill: "both",
iterations: Infinity,
easing: "ease-in-out"
}
);
});
.ball {
position: absolute;
border-radius: 100%;
opacity: 0.7;
}
#box{
width: 300px;
height: 300px;
border: 1px solid red;
position: relative;
overflow: hidden;
}
<div id="box"></div>
The issue is, you are appending balls to the body. That's why they are appearing all over the screen. You have to make a container with some width and height and append the created balls to that container only:
// Some random colors
const colors = ["#3CC157", "#2AA7FF", "#1B1B1B", "#FCBC0F", "#F85F36"];
const numBalls = 50;
const balls = [];
for (let i = 0; i < numBalls; i++) {
let ball = document.createElement("div");
ball.classList.add("ball");
ball.style.background = colors[Math.floor(Math.random() * colors.length)];
ball.style.left = `${Math.floor(Math.random() * 100)}vw`;
ball.style.top = `${Math.floor(Math.random() * 100)}vh`;
ball.style.transform = `scale(${Math.random()})`;
ball.style.width = `${Math.random()}em`;
ball.style.height = ball.style.width;
balls.push(ball);
document.querySelector('.ballContainer').append(ball);
}
// Keyframes
balls.forEach((el, i, ra) => {
let to = {
x: Math.random() * (i % 2 === 0 ? -11 : 11),
y: Math.random() * 12
};
let anim = el.animate(
[
{ transform: "translate(0, 0)" },
{ transform: `translate(${to.x}rem, ${to.y}rem)` }
],
{
duration: (Math.random() + 1) * 2000, // random duration
direction: "alternate",
fill: "both",
iterations: Infinity,
easing: "ease-in-out"
}
);
});
.ball {
position: absolute;
border-radius: 100%;
opacity: 0.7;
}
.ballContainer{
width: 350px;
height: 175px;
border: 1px solid gray;
position: relative;
overflow: hidden;
background-color: lightgray;
}
<div class="ballContainer">
</div>
The balls run a random path. That means some of balls will go out of bounds (your div).
You can add CSS to hide the scroll bars.
html * {
overflow: hidden;
}

Starter Question, overlay Div with Text over an Animation

I would like to overlay a div over another div.
But there are some problems because i am newbie.
// particle.min.js hosted on GitHub
// Scroll down for initialisation code
!function(a){var b="object"==typeof self&&self.self===self&&self||"object"==typeof global&&global.global===global&&global;"function"==typeof define&&define.amd?define(["exports"],function(c){b.ParticleNetwork=a(b,c)}):"object"==typeof module&&module.exports?module.exports=a(b,{}):b.ParticleNetwork=a(b,{})}(function(a,b){var c=function(a){this.canvas=a.canvas,this.g=a.g,this.particleColor=a.options.particleColor,this.x=Math.random()*this.canvas.width,this.y=Math.random()*this.canvas.height,this.velocity={x:(Math.random()-.5)*a.options.velocity,y:(Math.random()-.5)*a.options.velocity}};return c.prototype.update=function(){(this.x>this.canvas.width+20||this.x<-20)&&(this.velocity.x=-this.velocity.x),(this.y>this.canvas.height+20||this.y<-20)&&(this.velocity.y=-this.velocity.y),this.x+=this.velocity.x,this.y+=this.velocity.y},c.prototype.h=function(){this.g.beginPath(),this.g.fillStyle=this.particleColor,this.g.globalAlpha=.7,this.g.arc(this.x,this.y,1.5,0,2*Math.PI),this.g.fill()},b=function(a,b){this.i=a,this.i.size={width:this.i.offsetWidth,height:this.i.offsetHeight},b=void 0!==b?b:{},this.options={particleColor:void 0!==b.particleColor?b.particleColor:"#fff",background:void 0!==b.background?b.background:"#1a252f",interactive:void 0!==b.interactive?b.interactive:!0,velocity:this.setVelocity(b.speed),density:this.j(b.density)},this.init()},b.prototype.init=function(){if(this.k=document.createElement("div"),this.i.appendChild(this.k),this.l(this.k,{position:"absolute",top:0,left:0,bottom:0,right:0,"z-index":1}),/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(this.options.background))this.l(this.k,{background:this.options.background});else{if(!/\.(gif|jpg|jpeg|tiff|png)$/i.test(this.options.background))return console.error("Please specify a valid background image or hexadecimal color"),!1;this.l(this.k,{background:'url("'+this.options.background+'") no-repeat center',"background-size":"cover"})}if(!/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(this.options.particleColor))return console.error("Please specify a valid particleColor hexadecimal color"),!1;this.canvas=document.createElement("canvas"),this.i.appendChild(this.canvas),this.g=this.canvas.getContext("2d"),this.canvas.width=this.i.size.width,this.canvas.height=this.i.size.height,this.l(this.i,{position:"relative"}),this.l(this.canvas,{"z-index":"20",position:"relative"}),window.addEventListener("resize",function(){return this.i.offsetWidth===this.i.size.width&&this.i.offsetHeight===this.i.size.height?!1:(this.canvas.width=this.i.size.width=this.i.offsetWidth,this.canvas.height=this.i.size.height=this.i.offsetHeight,clearTimeout(this.m),void(this.m=setTimeout(function(){this.o=[];for(var a=0;a<this.canvas.width*this.canvas.height/this.options.density;a++)this.o.push(new c(this));this.options.interactive&&this.o.push(this.p),requestAnimationFrame(this.update.bind(this))}.bind(this),500)))}.bind(this)),this.o=[];for(var a=0;a<this.canvas.width*this.canvas.height/this.options.density;a++)this.o.push(new c(this));this.options.interactive&&(this.p=new c(this),this.p.velocity={x:0,y:0},this.o.push(this.p),this.canvas.addEventListener("mousemove",function(a){this.p.x=a.clientX-this.canvas.offsetLeft,this.p.y=a.clientY-this.canvas.offsetTop}.bind(this)),this.canvas.addEventListener("mouseup",function(a){this.p.velocity={x:(Math.random()-.5)*this.options.velocity,y:(Math.random()-.5)*this.options.velocity},this.p=new c(this),this.p.velocity={x:0,y:0},this.o.push(this.p)}.bind(this))),requestAnimationFrame(this.update.bind(this))},b.prototype.update=function(){this.g.clearRect(0,0,this.canvas.width,this.canvas.height),this.g.globalAlpha=1;for(var a=0;a<this.o.length;a++){this.o[a].update(),this.o[a].h();for(var b=this.o.length-1;b>a;b--){var c=Math.sqrt(Math.pow(this.o[a].x-this.o[b].x,2)+Math.pow(this.o[a].y-this.o[b].y,2));c>120||(this.g.beginPath(),this.g.strokeStyle=this.options.particleColor,this.g.globalAlpha=(120-c)/120,this.g.lineWidth=.7,this.g.moveTo(this.o[a].x,this.o[a].y),this.g.lineTo(this.o[b].x,this.o[b].y),this.g.stroke())}}0!==this.options.velocity&&requestAnimationFrame(this.update.bind(this))},b.prototype.setVelocity=function(a){return"fast"===a?1:"slow"===a?.33:"none"===a?0:.66},b.prototype.j=function(a){return"high"===a?5e3:"low"===a?2e4:isNaN(parseInt(a,10))?1e4:a},b.prototype.l=function(a,b){for(var c in b)a.style[c]=b[c]},b});
// Initialisation
var canvasDiv = document.getElementById('particle-canvas');
var options = {
particleColor: '#888',
background: 'https://raw.githubusercontent.com/JulianLaval/canvas-particle-network/master/img/demo-bg.jpg',
interactive: true,
speed: 'medium',
density: 'high'
};
var particleCanvas = new ParticleNetwork(canvasDiv, options);
html,
body {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
#particle-canvas {
width: 100%;
height: 100%;
}
h1 {
text-align: center;
position: inherit;
float: none;
}
#container {
width: 100px;
height: 100px;
}
#back,
.overstyle {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 50%;
}
.overstyle h1{
font-family: 'Open Sans Condensed', sans-serif;
font-size: 230%;
font-family: 'Indie Flower', cursive;
}
.overstyle {
text-align: center;
color: white;
z-index: 100;
}
<body>
<div id="container" style="width: 100%; height: 100%;">
<div id="particle-canvas" id="back"></div>
<script src='https://rawgit.com/JulianLaval/canvas-particle-network/master/particle-network.min.js'></script>
<script src="js/index.js"></script>
<!--- Content Div --->
<div style="width: 100%; color: rgba(255, 255, 255, 0.55);" class="overstyle">
<h1>TestFont</h1>
</div>
<!--- End of Content Div --->
</div>
</body>
The code is a div with id="particle-canavas". For more information about that visit this page here.
This is my Css stylesheet.
So now how could I improve the overlay div with class "overstyle"?
Thanks for all help.
This centers as you need.
When I need to center something, I always try:
width:100%;
margin-left:auto;
margin-right:auto;
// particle.min.js hosted on GitHub
// Scroll down for initialisation code
! function(a) {
var b = "object" == typeof self && self.self === self && self || "object" == typeof global && global.global === global && global;
"function" == typeof define && define.amd ? define(["exports"], function(c) {
b.ParticleNetwork = a(b, c)
}) : "object" == typeof module && module.exports ? module.exports = a(b, {}) : b.ParticleNetwork = a(b, {})
}(function(a, b) {
var c = function(a) {
this.canvas = a.canvas, this.g = a.g, this.particleColor = a.options.particleColor, this.x = Math.random() * this.canvas.width, this.y = Math.random() * this.canvas.height, this.velocity = {
x: (Math.random() - .5) * a.options.velocity,
y: (Math.random() - .5) * a.options.velocity
}
};
return c.prototype.update = function() {
(this.x > this.canvas.width + 20 || this.x < -20) && (this.velocity.x = -this.velocity.x), (this.y > this.canvas.height + 20 || this.y < -20) && (this.velocity.y = -this.velocity.y), this.x += this.velocity.x, this.y += this.velocity.y
}, c.prototype.h = function() {
this.g.beginPath(), this.g.fillStyle = this.particleColor, this.g.globalAlpha = .7, this.g.arc(this.x, this.y, 1.5, 0, 2 * Math.PI), this.g.fill()
}, b = function(a, b) {
this.i = a, this.i.size = {
width: this.i.offsetWidth,
height: this.i.offsetHeight
}, b = void 0 !== b ? b : {}, this.options = {
particleColor: void 0 !== b.particleColor ? b.particleColor : "#fff",
background: void 0 !== b.background ? b.background : "#1a252f",
interactive: void 0 !== b.interactive ? b.interactive : !0,
velocity: this.setVelocity(b.speed),
density: this.j(b.density)
}, this.init()
}, b.prototype.init = function() {
if (this.k = document.createElement("div"), this.i.appendChild(this.k), this.l(this.k, {
position: "absolute",
top: 0,
left: 0,
bottom: 0,
right: 0,
"z-index": 1
}), /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(this.options.background)) this.l(this.k, {
background: this.options.background
});
else {
if (!/\.(gif|jpg|jpeg|tiff|png)$/i.test(this.options.background)) return console.error("Please specify a valid background image or hexadecimal color"), !1;
this.l(this.k, {
background: 'url("' + this.options.background + '") no-repeat center',
"background-size": "cover"
})
}
if (!/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(this.options.particleColor)) return console.error("Please specify a valid particleColor hexadecimal color"), !1;
this.canvas = document.createElement("canvas"), this.i.appendChild(this.canvas), this.g = this.canvas.getContext("2d"), this.canvas.width = this.i.size.width, this.canvas.height = this.i.size.height, this.l(this.i, {
position: "relative"
}), this.l(this.canvas, {
"z-index": "20",
position: "relative"
}), window.addEventListener("resize", function() {
return this.i.offsetWidth === this.i.size.width && this.i.offsetHeight === this.i.size.height ? !1 : (this.canvas.width = this.i.size.width = this.i.offsetWidth, this.canvas.height = this.i.size.height = this.i.offsetHeight, clearTimeout(this.m), void(this.m = setTimeout(function() {
this.o = [];
for (var a = 0; a < this.canvas.width * this.canvas.height / this.options.density; a++) this.o.push(new c(this));
this.options.interactive && this.o.push(this.p), requestAnimationFrame(this.update.bind(this))
}.bind(this), 500)))
}.bind(this)), this.o = [];
for (var a = 0; a < this.canvas.width * this.canvas.height / this.options.density; a++) this.o.push(new c(this));
this.options.interactive && (this.p = new c(this), this.p.velocity = {
x: 0,
y: 0
}, this.o.push(this.p), this.canvas.addEventListener("mousemove", function(a) {
this.p.x = a.clientX - this.canvas.offsetLeft, this.p.y = a.clientY - this.canvas.offsetTop
}.bind(this)), this.canvas.addEventListener("mouseup", function(a) {
this.p.velocity = {
x: (Math.random() - .5) * this.options.velocity,
y: (Math.random() - .5) * this.options.velocity
}, this.p = new c(this), this.p.velocity = {
x: 0,
y: 0
}, this.o.push(this.p)
}.bind(this))), requestAnimationFrame(this.update.bind(this))
}, b.prototype.update = function() {
this.g.clearRect(0, 0, this.canvas.width, this.canvas.height), this.g.globalAlpha = 1;
for (var a = 0; a < this.o.length; a++) {
this.o[a].update(), this.o[a].h();
for (var b = this.o.length - 1; b > a; b--) {
var c = Math.sqrt(Math.pow(this.o[a].x - this.o[b].x, 2) + Math.pow(this.o[a].y - this.o[b].y, 2));
c > 120 || (this.g.beginPath(), this.g.strokeStyle = this.options.particleColor, this.g.globalAlpha = (120 - c) / 120, this.g.lineWidth = .7, this.g.moveTo(this.o[a].x, this.o[a].y), this.g.lineTo(this.o[b].x, this.o[b].y), this.g.stroke())
}
}
0 !== this.options.velocity && requestAnimationFrame(this.update.bind(this))
}, b.prototype.setVelocity = function(a) {
return "fast" === a ? 1 : "slow" === a ? .33 : "none" === a ? 0 : .66
}, b.prototype.j = function(a) {
return "high" === a ? 5e3 : "low" === a ? 2e4 : isNaN(parseInt(a, 10)) ? 1e4 : a
}, b.prototype.l = function(a, b) {
for (var c in b) a.style[c] = b[c]
}, b
});
// Initialisation
var canvasDiv = document.getElementById('particle-canvas');
var options = {
particleColor: '#888',
background: 'https://raw.githubusercontent.com/JulianLaval/canvas-particle-network/master/img/demo-bg.jpg',
interactive: true,
speed: 'medium',
density: 'high'
};
var particleCanvas = new ParticleNetwork(canvasDiv, options);
html,
body {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
#particle-canvas {
width: 100%;
height: 100%;
}
h1 {
text-align: center;
position: inherit;
float: none;
}
#container {
width: 100px;
height: 100px;
}
#back,
.overstyle {
width: 100%;
height: 100%;
position: absolute;
top: 0;
}
.overstyle h1{
font-family: 'Open Sans Condensed', sans-serif;
font-size: 230%;
font-family: 'Indie Flower', cursive;
width: 100%;
margin-left: auto;
margin-right: auto;
}
.overstyle {
text-align: center;
color: white;
z-index: 100;
}
<body>
<div id="container" style="width: 100%; height: 100%;">
<div id="particle-canvas" id="back"></div>
<script src='https://rawgit.com/JulianLaval/canvas-particle-network/master/particle-network.min.js'></script>
<script src="js/index.js"></script>
<!--- Content Div --->
<div style="width: 100%; color: rgba(255, 255, 255, 0.55);" class="overstyle">
<h1>TestFont</h1>
</div>
<!--- End of Content Div --->
</div>
</body>
I have tried it on the example you provided -
This worked for me:
.overstyle h1 {
z-index:2;
color: #fff;
position: absolute;
top: 20px;
left: 20px;
}

Div goes to wrong position

I am editing the vertical slider I used from this color picker. It uses top to adjust the cursors position. I want to use transform translateY instead. When I do that, I apparently have to calculate it differently.
The original calculation is: (Line: #286)
hsv_barcursor.style.top = ((1 - color.hsv.v) * hsv_barHeight) + 'px';
My updated version is: (Line 43 at JSFiddle Below)
hsv_barcursor.style.transform = 'translateY(calc(' + (color.RND.hsv.v * 10) + '% - ' + cursorRadius + 'px))';
The position of my version is wrong.
Why doesn't the math for top work for tranlateY?
What's the correct math to use for translateY?
I'm not looking for JQuery answers, nor am I looking for html's input range.
JSFiddle
var luminenceBarWrapper = document.getElementById('luminenceBarWrapper'),
hsv_barBGLayer = document.getElementById('bar-bg'),
hsv_barcursor = document.getElementById('hsv-barcursor'),
hsv_barCursors = document.getElementById('hsv-barcursors'),
hsv_barHeight = hsv_barCursors.offsetHeight,
cursorRadius = hsv_barcursor.offsetHeight / 2,
startPoint,
currentTarget,
myColor = new Colors();
// Create Event Functions
var hsvDown = function(e) { // mouseDown callback
e.preventDefault();
if (e.target === hsv_barcursor) currentTarget = e.target.parentNode;
else if (e.target === hsv_barCursors) currentTarget = e.target;
else return;
startPoint = getOrigin(currentTarget);
window.addEventListener('mousemove', hsvMove);
hsvMove(e);
startRender();
},
hsvMove = function(e) { // mouseMove callback
myColor.setColor({
v: (hsv_barHeight - (e.clientY - startPoint.top)) / hsv_barHeight * 100
}, 'hsv');
};
// Initial Rendering
doRender(myColor.colors);
// Adde Events To Objects
luminenceBarWrapper.addEventListener('mousedown', hsvDown);
window.addEventListener('mouseup', function() {
window.removeEventListener('mousemove', hsvMove);
stopRender();
});
function doRender(color) {
hsv_barcursor.style.transform = 'translateY(calc(' + (color.RND.hsv.v * 10) + '% - ' + cursorRadius + 'px))';
//hsv_barcursor.style.top = ((1 - color.hsv.v) * hsv_barHeight) + 'px';
}
var renderTimer,
startRender = function(oneTime) {
renderTimer = window.setInterval(function() {
doRender(myColor.colors);
}, 13); // 1000 / 60); // ~16.666 -> 60Hz or 60fps
},
stopRender = function() {
window.clearInterval(renderTimer);
};
function getOrigin(elm) {
var box = (elm.getBoundingClientRect) ? elm.getBoundingClientRect() : {
top: 0,
left: 0
},
doc = elm && elm.ownerDocument,
body = doc.body,
win = doc.defaultView || doc.parentWindow || window,
docElem = doc.documentElement || body.parentNode,
clientTop = docElem.clientTop || body.clientTop || 0, // border on html or body or both
clientLeft = docElem.clientLeft || body.clientLeft || 0;
return {
left: box.left + (win.pageXOffset || docElem.scrollLeft) - clientLeft,
top: box.top + (win.pageYOffset || docElem.scrollTop) - clientTop
};
}
body {
position: absolute;
}
#bar-bg {
width: 15px;
height: 500px;
background-color: greenyellow;
}
#hsv-barcursors {
position: absolute;
right: -7.5px;
width: 30px;
top: 0;
height: 500px;
overflow: hidden;
cursor: pointer;
}
#hsv-barcursor {
position: absolute;
right: 7.5px;
width: 11px;
height: 11px;
border-radius: 50%;
border: 2px solid black;
margin-bottom: 5px;
}
<script src="https://rawgit.com/PitPik/colorPicker/master/colors.js"></script>
<div id="luminenceBarWrapper">
<div id="bar-bg"></div>
<div id="hsv-barcursors" id="hsv_cursors">
<div id="hsv-barcursor"></div>
</div>
</div>
translateY(Npx) translates something downward if N > 0 therefore you need translate(0px) when the marker is up and translate(Npx) when the marker is down at the bottom of your component
Your variables are
color.RND.hsv.v which is 100 when the marker is up and 0 when the marker is down
hsv_barHeight is the height of your component
The first step is to invert the value of color.RND.hsv.v i.e. (100 - color.RND.hsv.v) next we will map it to the range [0,1] which is simply done by a simple division i.e. t = (100 - color.RND.hsv.v) / 100 we do this so that the height is linearly mapped with this value t to be in the range [0, height] i.e. (100 - color.RND.hsv.v) / 100 * hsv_barHeight which is the final equation
var luminenceBarWrapper = document.getElementById('luminenceBarWrapper'),
hsv_barBGLayer = document.getElementById('bar-bg'),
hsv_barcursor = document.getElementById('hsv-barcursor'),
hsv_barCursors = document.getElementById('hsv-barcursors'),
hsv_barHeight = hsv_barCursors.offsetHeight,
cursorRadius = hsv_barcursor.offsetHeight / 2,
startPoint,
currentTarget,
myColor = new Colors();
// Create Event Functions
var hsvDown = function(e) { // mouseDown callback
e.preventDefault();
if (e.target === hsv_barcursor) currentTarget = e.target.parentNode;
else if (e.target === hsv_barCursors) currentTarget = e.target;
else return;
startPoint = getOrigin(currentTarget);
window.addEventListener('mousemove', hsvMove);
hsvMove(e);
startRender();
},
hsvMove = function(e) { // mouseMove callback
myColor.setColor({
v: (hsv_barHeight - (e.clientY - startPoint.top)) / hsv_barHeight * 100
}, 'hsv');
};
// Initial Rendering
doRender(myColor.colors);
// Adde Events To Objects
luminenceBarWrapper.addEventListener('mousedown', hsvDown);
window.addEventListener('mouseup', function() {
window.removeEventListener('mousemove', hsvMove);
stopRender();
});
function doRender(color) {
hsv_barcursor.style.transform = 'translateY(' + (100 - color.RND.hsv.v) / 100 * (hsv_barHeight - cursorRadius) + 'px)';
//hsv_barcursor.style.top = ((1 - color.hsv.v) * hsv_barHeight) + 'px';
}
var renderTimer,
startRender = function(oneTime) {
renderTimer = window.setInterval(function() {
doRender(myColor.colors);
}, 13); // 1000 / 60); // ~16.666 -> 60Hz or 60fps
},
stopRender = function() {
window.clearInterval(renderTimer);
};
function getOrigin(elm) {
var box = (elm.getBoundingClientRect) ? elm.getBoundingClientRect() : {
top: 0,
left: 0
},
doc = elm && elm.ownerDocument,
body = doc.body,
win = doc.defaultView || doc.parentWindow || window,
docElem = doc.documentElement || body.parentNode,
clientTop = docElem.clientTop || body.clientTop || 0, // border on html or body or both
clientLeft = docElem.clientLeft || body.clientLeft || 0;
return {
left: box.left + (win.pageXOffset || docElem.scrollLeft) - clientLeft,
top: box.top + (win.pageYOffset || docElem.scrollTop) - clientTop
};
}
body {
position: absolute;
}
#bar-bg {
width: 15px;
height: 500px;
background-color: greenyellow;
}
#hsv-barcursors {
position: absolute;
right: -7.5px;
width: 30px;
top: 0;
height: 500px;
overflow: hidden;
cursor: pointer;
}
#hsv-barcursor {
position: absolute;
right: 7.5px;
width: 11px;
height: 11px;
border-radius: 50%;
border: 2px solid black;
margin-bottom: 5px;
}
<script src="https://rawgit.com/PitPik/colorPicker/master/colors.js"></script>
<div id="luminenceBarWrapper">
<div id="bar-bg"></div>
<div id="hsv-barcursors" id="hsv_cursors">
<div id="hsv-barcursor"></div>
</div>
</div>

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>

Categories