HTML preprocessors can make writing HTML more powerful or convenient. For instance, Markdown is designed to be easier to write and read for text documents and you could write a loop in Pug.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. So you don't have access to higher-up elements like the <html>
tag. If you want to add classes there that can affect the whole document, this is the place to do it.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. If you need things in the <head>
of the document, put that code here.
The resource you are linking to is using the 'http' protocol, which may not work when the browser is using https.
CSS preprocessors help make authoring CSS easier. All of them offer things like variables and mixins to provide convenient abstractions.
It's a common practice to apply CSS to a page that styles elements such that they are consistent across all browsers. We offer two of the most popular choices: normalize.css and a reset. Or, choose Neither and nothing will be applied.
To get the best cross-browser support, it is a common practice to apply vendor prefixes to CSS properties and values that require them to work. For instance -webkit-
or -moz-
.
We offer two popular choices: Autoprefixer (which processes your CSS server-side) and -prefix-free (which applies prefixes via a script, client-side).
Any URLs added here will be added as <link>
s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.
You can apply CSS to your Pen from any stylesheet on the web. Just put a URL to it here and we'll apply it, in the order you have them, before the CSS in the Pen itself.
You can also link to another Pen here (use the .css
URL Extension) and we'll pull the CSS from that Pen and include it. If it's using a matching preprocessor, use the appropriate URL Extension and we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
JavaScript preprocessors can help make authoring JavaScript easier and more convenient.
Babel includes JSX processing.
Any URL's added here will be added as <script>
s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.
You can apply a script from anywhere on the web to your Pen. Just put a URL to it here and we'll add it, in the order you have them, before the JavaScript in the Pen itself.
If the script you link to has the file extension of a preprocessor, we'll attempt to process it before applying.
You can also link to another Pen here, and we'll pull the JavaScript from that Pen and include it. If it's using a matching preprocessor, we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
Search for and use JavaScript packages from npm here. By selecting a package, an import
statement will be added to the top of the JavaScript editor for this package.
Using packages here is powered by esm.sh, which makes packages from npm not only available on a CDN, but prepares them for native JavaScript ESM usage.
All packages are different, so refer to their docs for how they work.
If you're using React / ReactDOM, make sure to turn on Babel for the JSX processing.
If active, Pens will autosave every 30 seconds after being saved once.
If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.
If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.
Visit your global Editor Settings.
<link href="//cdn.muicss.com/mui-0.5.1/css/mui.min.css" rel="stylesheet" type="text/css" />
<script src="//cdnjs.cloudflare.com/ajax/libs/es6-promise/3.2.1/es6-promise.min.js"></script>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//cdn.muicss.com/mui-0.5.1/js/mui.min.js"></script>
<script>
/**
* @constructor DashTimer
* uses D3 to plot circular timer
* @param {object|string} div the container for the viz an element or #name
*/
function DashTimer(div) {
var TAU = 2 * Math.PI,
NAME = "DashTimer";
var self = this,
data_, dash_,
dInfo_ = {},
div_ = Object(div) === div ? '#' + div.id : div;
/**
* initialize the arc data
* @param {[object]} data the array of data for each arc
* @param {object} [options=] the viz options
* @return {DashTimer} self
*/
self.setData = function(data, options) {
// first time in, set up the containers
if (!dash_) {
self.init(options)
}
// set up values for d3
data_ = (data || [{}, {}]).map(function(d, i) {
var m = vanMerge_([dash_, d]);
m.index = i;
m.startAngle = m.start.angle * TAU;
m.endAngle = m.finish.angle * TAU;
m.start.innerRadius = dash_.height / 2 * m.start.innerRatio;
m.start.outerRadius = dash_.height / 2 * m.start.outerRatio;
m.innerRadius = m.start.innerRadius;
m.outerRadius = m.start.outerRadius;
m.finish.innerRadius = dash_.height / 2 * m.finish.innerRatio;
m.finish.outerRadius = dash_.height / 2 * m.finish.outerRatio;
m.value = m.start.value;
m.static = {
endAngle: m.immediate.angle,
innerRadius: m.finish.innerRadius === m.start.innerRadius,
outerRadius: m.finish.outerRadius === m.start.outerRadius,
value: m.start.value === m.finish.value
};
m.dataName = m.dataName || NAME + i;
return m;
});
d3.select(div_).html("");
dInfo_.arc = d3.svg.arc();
dInfo_.svg = d3.select(div_).append("svg")
.attr("width", dash_.width)
.attr("height", dash_.height)
.append("g")
.attr("transform", "translate(" +
dash_.width / 2 + "," + dash_.height / 2 + ")");
dInfo_.textElements = dInfo_.svg.append("text");
(dash_.values.styles.split(";") || []).forEach(function(d) {
if (d) {
var s = d.split(":");
if (s.length !== 2) throw 'invalid style ' + d;
dInfo_.textElements.style(s[0], s[1]);
}
});
if (dash_.values.classes) {
dash_.values.classes.split(" ").forEach(function(d) {
if (d) dInfo_.textElements.classed(d, true);
});
}
dash_.internal.cancelled = dash_.internal.paused= dash_.internal.finished= false;
resetPromise_();
return self;
};
/**
* initialize the arcs
* @param {object} options the viz options
* @return {DashTimer} self
*/
self.init = function(options) {
// merge default settings with given
dash_ = vanMerge_([{ // default settings - many can be overridden in data for individual items
height: 100, // height of viz
width: 100, // width of viz (since a circle both are equal)
ease: "linear", // type of easing (pause/resume will only work reliably with linear)
duration: 5000, // tranistion duration
callback:"", // function (d, self) to call back on every transition step
name: NAME + new Date().getTime(), // name of dashtimer
start: { // values at start of transition
innerRatio: .8, // diameter of inner circle relative to height
outerRatio: .95, // diameter of outer circle relative to height
angle: 0, // angle between 0 and 1
fill: '#2196F3', // fill color
value: 0 // value to interpolate
},
finish: { // values at end of transition
innerRatio: .8, // diameter of inner circle relative to height
outerRatio: .95, // diameter of outer circle relative to height
angle: 1, // angle between 0 and 1
fill: '#FFC107', // fill color
value: 100 // value to interpolate
},
immediate: { // if true then transition is skipped
angle:false, // the angle
},
values: { // things to do with displaying values
classes: "", // classes to apply separated by spaces
styles: "text-anchor:middle;", // styles to apply separated by ;
show: false, // whether to show values
decorate: function(d) { // function to clean up data before displaying
return Math.round(d);
}
},
custom:{}, // should be used for any custom data to be carried around
internal:{
paused:false,
progress:0,
finished:true
}
}, options || {}]);
return self;
};
/**
* causes the timer to resolve from outside
* @return {DashTimer} self
*/
self.resolve= function() {
dash_.internal.paused = false;
dash_.internal.finished = true;
dash_.mp.resolve(self);
return self;
};
/**
* causes the timer to reject from outside
* @return {DashTimer} self
*/
self.reject = function() {
dash_.internal.paused = false;
dash_.internal.finished = true;
dash_.mp.reject(self);
return self;
};
/**
* pauses the timer right now
* timeout promise is not resolved or rejected
* @return {DashTimer} self
*/
self.pause = function() {
if (self.isRunning()) {
dash_.internal.paused = true;
dInfo_.path.transition(dash_.name).duration(0);
}
return self;
};
/**
* resumes at the last place paused at
* if not paused, nothing happens
* @return {Promise | null} a promise to th resumed run - not normally required
*/
self.resume = function() {
// this will return the master promise
if (self.isPaused()) {
return work_(
dash_.internal.progress,
dash_.internal.duration * (dash_.finishAt - dash_.internal.progress),
dash_.finishAt
);
}
};
/**
* kills any ongoing transition
* does not fire any promises
* @return {DashTimer} self
*/
self.cancel = function() {
//if there is oe on the go then kill it and reject the promise
if (dash_.mp) {
dash_.internal.cancelled = true;
// this will cause a zero transition to cancel the current one
dInfo_.path.transition(dash_.name).duration(0);
self.reject();
}
}
/**
* start a new timer
* @param {number} duration number of seconds to run he transition for
* @param {[object]} [data=] replace the current data with this
* @param {number} [start=0] start place
* @param {number} [finish=1] finish place
* @return {Promise} a promise that'll be resolved when it times out
*/
self.start = function(duration,start,finish) {
resetPromise_();
dash_.internal.duration = fixDef_ (duration,dash_.duration);
return work_(fixDef_ (start,0) , dash_.internal.duration, fixDef_ (finish,1));
};
/**
* get the current data
* @return {[object]} the data
*/
self.getData = function() {
return data_;
};
/**
* get the current data
* @param {string} dataName the data name
* @return {object} the data
*/
self.getItem = function(dataName) {
return data_.reduce(function(p,c) {
return c.dataName === dataName ? c : p;
},null);
};
/**
* get all the current control values
* @return {object} the current control values
*/
self.getControl = function() {
return dash_;
};
/**
* get all the current control values
* @return {object} the current control values
*/
self.getVizInfo = function() {
return dInfo_;
};
/**
* is the viz currenly paused
* @return {boolean} is it paused
*/
self.isPaused = function() {
return dash_.internal.paused;
};
/**
* is the viz currenly finished
* @return {boolean} is it finished
*/
self.isFinished = function() {
return dash_.internal.finished;
};
/**
* is the viz currenly running
* @return {boolean} is it running
*/
self.isRunning = function() {
return !self.isFinished() && !self.isPaused();
};
/**
* is the viz currenly cancelled
* @return {boolean} is it running
*/
self.isCancelled = function() {
return dash_.internal.cancelled;
};
/**
* get the current progress
* @return {number} between 0 and 1
*/
self.getProgress = function() {
return dash_.internal.progress;
};
/**
* set the progress immediately and stop the viz
* the completion promise is rejected
* @param {number} between 0 and 1 to set it to
* @param {number} [duration=0] how long to take to do it
* @return {Promise} the master promise
*/
self.setProgress = function(progress, duration) {
return work_(0, fixDef_ (duration ,0 ), progress);
};
/**
* reset the paths for the arcs
*/
function redoPath_() {
// redo the path
dInfo_.path = dInfo_.svg.selectAll("path").data(data_);
// add the new data
dInfo_.enter = dInfo_.path.enter().append("path")
.attr("fill", function(d) {
return d.start.fill
})
.attr("d", dInfo_.arc);
// get rid of any old
dInfo_.path.exit().remove();
}
/**
* make a new master promise
* @return {DashTimer} self
*/
function resetPromise_() {
dash_.mp = {};
dash_.mp.promise = new Promise(function(resolve, reject) {
dash_.mp.reject = reject;
dash_.mp.resolve = resolve;
});
return self;
}
/**
* co-ordinate the work, and manage the completion promises
* @param {number} start place to start at between 0 and 1
* @param {number} duration number of seconds to run he transition for
* @param {number} [finish=1] place to stop at between 0 and 1
* @return {Promise} a promise that'll be resolved when it times out
*/
function work_(start, duration, finish) {
// tidy up from last time
redoPath_();
finish = fixDef_ ( finish,1);
// we'll need this if resuming
dash_.finishAt = finish;
dash_.startAt = start;
// this returns a promise that simply resolves the master promise
show_(start, duration, finish)
.then(
function(expired) {
self.resolve();
},
function(stopped) {
// nothing to do here - we dont want to resolve or reject the master promise
// because this was just a pause
});
// return the master promise
return dash_.mp.promise;
}
/**
* do the transition
* @param {number} startAt place to start at between 0 and 1
* @param {number} duration number of seconds to run he transition for
* @param {number} finishAt place to stop at between 0 and 1
* @return {Promise} a promise that'll be resolved when it times out
*/
function show_(startAt, duration, finishAt) {
dash_.internal.paused = dash_.internal.finished = dash_.internal.cancelled = false, dash_.internal.progress = 0;
// this is a promise that will be resolved when the timer runs out
return new Promise(function(resolve, reject) {
// interpolation include any startat/finishat generated by a pause
// this returns a closure with the modified interpolation function
function rng(a, b) {
return d3.interpolate(
d3.interpolate(a, b)(startAt), d3.interpolate(a, b)(1)
);
}
dInfo_.path.transition(dash_.name)
.ease(dash_.ease)
.duration(duration)
.each("end", function(d, i) {
if (i === data_.length - 1) {
dash_.internal.finished = true;
resolve(self)
}
})
.attrTween("d", function(d) {
// these interpolate closures for each of the transitioing values
d.interpolate = {
innerRadius: rng(d.start.innerRadius, d.finish.innerRadius),
outerRadius: rng(d.start.outerRadius, d.finish.outerRadius),
endAngle: rng(d.start.angle * TAU, d.finish.angle * TAU),
value: rng(d.start.value, d.finish.value)
};
// returns a closure for each tick
return function(t) {
// if a puase has been called in the meantime
// a transition pause is signalled by rejecting the promise
if (self.isPaused()) reject(self);
// statics mean that they dont transition
Object.keys(d.interpolate).forEach(function(k) {
if (!d.static[k]) d[k] = d.interpolate[k](t * finishAt);
});
dash_.internal.progress = (startAt + (1 - startAt) * t * finishAt);
// callback if requested
if(d.callback) d.callback(d , self , t);
// show the interpolated value
if (d.values.show) {
dInfo_.textElements.text(d.values.decorate(d.value));
}
return dInfo_.arc(d);
};
})
.styleTween("fill", function(d) {
return d3.interpolate(
d3.interpolate(d.start.fill, d.finish.fill)(startAt),
d3.interpolate(d.start.fill, d.finish.fill)(finishAt));
});
});
};
/**
* recursively extend an object with other objects
* @param {[object]} obs the array of objects to be merged
* @return {object} the extended object
*/
function vanMerge_(obs) {
return (obs || []).reduce(function(p, c) {
return vanExtend_(p, c);
}, {});
}
function vanExtend_(result, opt) {
result = result || {};
return Object.keys(opt).reduce(function(p, c) {
// if its an object
if (typeof opt[c] === "object") {
p[c] = vanExtend_(p[c], opt[c]);
} else {
p[c] = opt[c];
}
return p;
}, result);
}
function fixDef_ (value , defValue) {
return typeof value === typeof undefined ? defValue : value;
}
};
</script>
<div class="mui-panel mui--text-center">
<div id="clock">
</div>
</div>
<div id="result"></div>
<button id="start">Start</button>
<button id="pause">Pause</button>
<button id="resume">Resume</button>
<button id="cancel">Cancel</button>
function getClockData() {
var now = new Date();
return [{
dataName: 'hoursShadow',
start: {
fill: '#B3E5FC',
outerRatio: .7,
innerRatio: .55,
angle: 0
},
finish: {
fill: '#B3E5FC',
outerRatio: .7,
innerRatio: .55,
angle: now.getHours() / 24
},
immediate: {
angle: true
}
}, {
dataName: 'minutesShadow',
start: {
fill: '#F0F4C3',
outerRatio: .85,
innerRatio: .7,
angle: 0
},
finish: {
fill: '#F0F4C3',
outerRatio: .85,
innerRatio: .7,
angle: now.getMinutes() / 60
},
immediate: {
angle: true
}
}, {
dataName: "hours",
start: {
angle: now.getHours() / 24,
outerRatio: .7,
innerRatio: .55,
fill: '#B3E5FC'
},
finish: {
angle: 1/60/24 + (now.getHours() / 24),
outerRatio: .7,
innerRatio: .55,
fill: '#B3E5FC'
}
}, {
dataName: "minutes",
start: {
angle: now.getMinutes() / 60,
outerRatio: .85,
innerRatio: .7,
fill: '#F0F4C3'
},
finish: {
angle: 1/60 + (now.getMinutes() / 60),
outerRatio: .85,
innerRatio: .7,
fill: '#F0F4C3'
}
}, {
dataName: "seconds",
start: {
angle: now.getSeconds() / 60,
outerRatio: 2,
innerRatio: .85,
fill: '#E1BEE7'
},
finish: {
angle: 1 + (now.getSeconds() / 60),
outerRatio: 2,
innerRatio: .85,
fill: '#FFEB3B'
},
values: {
show: true
},
}
];
}
var clockOptions = {
duration: 60 * 1000,
values: {
classes: "mu--text-caption",
styles: "font-size:.7em;text-anchor:middle;",
decorate: function(d) {
return d3.time.format("%X")(new Date());
}
}
};
var timer = new DashTimer('#clock').init(clockOptions);
endless();
d3.select('#cancel').on("click", function() {
timer.cancel();
});
d3.select('#pause').on("click", function() {
timer.pause();
});
d3.select('#resume').on("click", function() {
timer.resume();
});
d3.select('#start').on("click", function() {
endless();
});
function endless() {
timer.setData(getClockData());
timer.start()
.then(
function(expired) {
endless();
},
function(cancelled) {
d3.select('#result').text('cancelled');
}
)
}
Also see: Tab Triggers