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.
header
h1 Generate date&time random
#app
.results
.section-header.section-header__results
h2 Results
//- コピー
button.button.is-rounded.is-small.copy-results.is-text(:data-clipboard-text="copyAllResults", title="copy")
span.icon.is-small
i.fas.fa-copy
//- 再生成
button.button.is-rounded.is-small.copy-results.is-text(@click.prevent="generateTimes", :data-clipboard-text="copyAllResults", title="generate")
span.icon.is-small
i.fas.fa-sync-alt
.section-header-menu
.select.is-rounded.is-small
select(v-model="options.sort")
option(value="random") random
option(value="newest") newest
option(value="oldest") oldest
.select.is-rounded.is-small
select(v-model="options.num")
option(value="10") 10
option(value="25") 25
option(value="50") 50
option(value="100") 100
option(value="250") 250
option(value="500") 500
table.table.is-fullwidth
thead
tr
th #
th time
tbody
tr(v-for="time, index in results")
td.index {{ index + 1 }}
td {{ time }}
.options
.section-header
h2 Options
//- 時間のフォーマット
div.field
label.label Format
.options-item
input.input(type="text", placeholder="YYYY/MM/DD HH:mm:ss", v-model="options.format")
ul.help
li: a.button.is-text.is-small(@click.prevent="changeFormat('YYYY/MM/DD HH:mm')") 2016/12/31 01:23
li: a.button.is-text.is-small(@click.prevent="changeFormat('YYYY/MM/DD')") 2016/12/31
li: a.button.is-text.is-small(@click.prevent="changeFormat('HH:mm')") 16:23
li: a.button.is-text.is-small(@click.prevent="changeFormat('YYYY/MM/DD (ddd) HH:mm:ss')") 2016/12/31 (Feb) 01:23:45
li: a.button.is-text.is-small(@click.prevent="changeFormat('dddd, MMMM Do YYYY, h:mm:ss a')") Feburary, December 31th 2016, 1:23:45 am
li: a.button.is-text.is-small(@click.prevent="changeFormat('H:mm:ss.SSS')") 16:23:45.012
li: a.button.is-text.is-small(@click.prevent="changeFormat('X')") Unix Timestamp
li: a.button.is-text.is-small(@click.prevent="changeFormat('x')") Unix Millisecond Timestamp
li: a.button.is-text.is-small(@click.prevent="changeFormat('Qo')") Quarter
li: a.button.is-text.is-small(@click.prevent="changeFormat('DDDo')") Day of Year
li: a.button.is-text.is-small(@click.prevent="changeFormat('Wo')") Week of Year
//- 範囲
div.field
label.label Range
.options-item
.columns.is-mobile.is-variable.is-1
.column
.input-date
label.label.is-small start
input.input.is-small(placeholder="YYYY/MM/DD", v-model="options.range.start", :class="{ 'is-danger': range.start.isValid === false }")
.column
.input-date
label.label.is-small end
input.input.is-small(placeholder="YYYY/MM/DD", v-model="options.range.end", :class="{ 'is-danger': range.end.isValid === false }")
//- プリセット
ul.help
li: a.button.is-text.is-small(@click.prevent="changeRange('today')") today
li: a.button.is-text.is-small(@click.prevent="changeRange('this week')") this week
li: a.button.is-text.is-small(@click.prevent="changeRange('this month')") this month
li: a.button.is-text.is-small(@click.prevent="changeRange('this quarter')") this quarter
li: a.button.is-text.is-small(@click.prevent="changeRange('this year')") this year
//- 結果から除外する日時
div.field
label.label Not (date or time)
.options-item
.columns.is-mobile.is-variable.is-1(v-for="(item, index) in options.not")
.column
.input-date
label.label.is-small start
input.input.is-small(placeholder="YYYY/MM/DD or HH:mm", v-model=`item.start`, :class="{ 'is-danger': not[index].isInput && not[index].isValid === false }")
.column
.input-date
label.label.is-small end
input.input.is-small(placeholder="YYYY/MM/DD or HH:mm", v-model=`item.end`, :class="{ 'is-danger': not[index].isInput && not[index].isValid === false }")
script(defer, src="https://use.fontawesome.com/releases/v5.3.1/js/all.js")
$sidebar-width: 260px;
html {
background-color: #f5f5f5;
}
body {
margin: 20px;
}
header {
padding-top: 2rem;
padding-bottom: 2rem;
h1 {
font-size: 1.6rem;
font-weight: bold;
}
}
#app {
width: 100%;
display: flex;
align-items: flex-start;
}
// オプション設定
.options {
display: flex;
width: $sidebar-width;
flex-wrap: wrap;
flex-direction: column;
border-radius: 6px;
.field {
&:not(:last-of-type) {
margin-bottom: 2rem;
}
}
.label.is-small {
color: #757575;
}
}
.options-item {
padding: 10px;
background-color: #e0e0e0;
border-radius: 2px;
&:not(:last-of-type) {
margin-bottom: 1rem;
}
}
// 結果
.results {
width: calc(100% - #{$sidebar-width});
box-sizing: border-box;
margin-right: 20px;
.index {
width: 2rem;
color: #aaaaaa;
}
}
.section-header {
display: flex;
padding-top: 0.6rem;
padding-bottom: 0.6rem;
h2 {
font-weight: bold;
}
}
.section-header__results {
position: sticky;
top: 0;
background-color: #f5f5f5;
h2 {
margin-right: 1rem;
}
* {
&:nth-child(4) {
// 右寄せ
margin-left: auto;
}
}
}
// デフォルトの範囲設定
const end = moment();
const start = end.subtract(1, 'years');
// 時間、日付の正規表現
const timeFormat = new RegExp(/^\s*\d{1,2}\s*:\s*\d{1,2}\s*[\.|:]?\d+$/);
const dateFormat = new RegExp(/^\s*\d{1,2}\s*\/\s*\d{1,2}$/);
// TODO optionsをcookieに保存する?
// TODO 含まない期間を指定できるようにする(複数のstart, end)
// 時間だけのとき、期間のときがある
const state = {
times: [], // 生成した年月日
// 設定(入力 or 選択される)
options: {
num: 10,
sort: 'random',
format: 'YYYY/MM/DD HH:mm',
range: {
// とりあえず今年にしておく
start: moment().startOf('year').format('YYYY/M/D'),
end: moment().endOf('year').format('YYYY/M/D')
},
// 結果から除外する日時
not: [
{start: null, end: null},
{start: null, end: null},
{start: null, end: null}
]
}
};
/**
* 有効な範囲を取得する
*
* @param range { start: number, end: number } 日時の範囲
* @param not { start: number, end: number }[] 含めない範囲
* @return { start: number, end: number }[] 有効な範囲のリスト
*/
function effectiveRange(range, not) {
let results = [
{start: range.start, end: range.end}
];
// notの範囲を削除していく
// notは昇順になっているのが前提
not.forEach((notItem) => {
for (let i = 0, len = results.length; i < len; i++) {
let result = results[i];
if (result !== null) {
if (notItem.start <= result.start && result.end <= notItem.end) {
// すべての範囲がnot
// 削除
results[i] = null;
} else if (notItem.start <= result.start && result.start <= notItem.end) {
// 前半がnot
// notで分割する
results[i] = {
start: notItem.end + 1,
end: result.end
};
} else if (notItem.start <= result.end && result.end <= notItem.end) {
// 後半がnot
results[i] = {
start: result.start,
end: notItem.start - 1
};
} else if (result.start <= notItem.start && notItem.end <= result.end) {
// 途中がnot
// 分割する
results[i] = {
start: result.start,
end: notItem.start - 1
};
const div = {
start: notItem.end + 1,
end: result.end
};
results.splice(i + 1, 0, div);
}
}
}
});
// console.log('effective range', results);
// nullの要素を削除する
return _.compact(results);
}
/**
* 指定の時間がnotに含まれているかを判別
*
* @param time {number} 時間(ミリ秒)
* @param notTimes {{ start: number, end: number, isCrossOverDay: boolean }} Notの時間(ミリ秒)
* @param {boolean} Notに含まれているか
*/
function isNotTime(time, notTimes) {
let result = false;
const len = notTimes.length;
for (let i = 0; i < len; i++) {
const item = notTimes[i];
if (item.isCrossOverDay === false) {
if (item.start <= time && time <= item.end) {
// Notに含まれている
result = true;
break;
}
} else {
// 0:00をまたぐ場合
if ((item.start <= time && time < 86400000) || time <= item.end) {
// Notに含まれている
result = true;
break;
}
}
}
return result;
}
new Vue({
el: '#app',
data: state,
created: function () {
this.generateTimes();
},
methods: {
/**
* 年月日を生成する
*
*/
generateTimes: function () {
const times = [];
// console.log('start', this.range.start, moment(this.range.start.date, 'x').format('YYYY/M/D HH:mm:ss'), 'end', this.range.end, moment(this.range.end.date, 'x').format('YYYY/M/D HH:mm:ss'));
// 有効なRange(Range内の日時からNotを取り除いた)をリストにする
// Notの種類が期間('period')のものを省く
// 含まない期間を選択
const notDates = this.not.filter((item) => {
return item.type === 'period' ? {start: item.start, end: item.end} : false;
});
// 含まない時間を抽出して、比較しやすいフォーマットに変換する
const notTimes = this.not.filter((item) => item.type === 'interval').map((item) => {
const startTime = item.start.split(':');
const endTime = item.end.split(':');
const weight = [60 * 60 * 1000, 60 * 1000, 1000]; // ミリ秒に変換するため時間
// HH:mm:ddをミリ秒に変換
const start = startTime.map((item, index) => item * weight[index]).reduce((prev, current) => prev + current);
const end = endTime.map((item, index) => item * weight[index]).reduce((prev, current) => prev + current);
return {
start,
end,
isCrossOverDay: end < start // 日をまたぐか(23:00 - 5:00のような場合)
};
});
// {
// type: "date"
// isInput: true
// isValid: true
// start: 1514736072
// end: 1514769000
// }
// Notを省いた期間のリストを生成
const rangeGroup = effectiveRange({
start: this.range.start.date,
end: this.range.end.date
}, notDates);
const numRangeGroup = rangeGroup.length; // 期間のグループ数
// 表示の最大件数分つくっておく
// 件数やフォーマットを変えたときに再生成させないため
for (let i = 0; i < 500; i++) {
const hit = _.random(numRangeGroup - 1); // どのグループから選ぶか
let generated = null;
while (generated === null) {
// for (let j = 0; j < 10; j++) {
generated = moment(_.random(rangeGroup[hit].start, rangeGroup[hit].end, false), 'x');
// Notの時間だったら作り直す
const time = Number(generated.format('H')) * 60 * 60 * 1000
+ Number(generated.format('m')) * 60 * 1000
+ Number(generated.format('s')) * 1000;
const result = isNotTime(time, notTimes);
// console.log(time, generated.format('H:m:s'), result);
// 含めない時間 or 不正な日付だったら作り直す
// Notに日付をまたぐ時間を指定したときに、invalidな日時になることがある?
if (result === true || generated.isValid() === false) {
generated = null;
}
}
times.push(generated);
}
this.times = times;
},
changeFormat: function (format) {
this.options.format = format;
},
/**
* 範囲プリセットから変更する
*
* @param label {string} プリセット名
*/
changeRange: function (label) {
switch (label) {
case 'today': // 今日
this.options.range.start = moment().startOf('date').format('YYYY/M/D');
this.options.range.end = moment().endOf('date').format('YYYY/M/D');
break;
case 'this week': // 今週
this.options.range.start = moment().startOf('week').format('YYYY/M/D');
this.options.range.end = moment().endOf('week').format('YYYY/M/D');
break;
case 'this month': // 今月
this.options.range.start = moment().startOf('month').format('YYYY/M/D');
this.options.range.end = moment().endOf('month').format('YYYY/M/D');
break;
case 'this quarter': // 今期
this.options.range.start = moment().startOf('quarter').format('YYYY/M/D');
this.options.range.end = moment().endOf('quarter').format('YYYY/M/D');
break;
case 'this year': // 今年
this.options.range.start = moment().startOf('year').format('YYYY/M/D');
this.options.range.end = moment().endOf('year').format('YYYY/M/D');
break;
}
}
},
computed: {
/**
* 表示するための時間を生成
*
*/
results: function () {
const tmp = _.cloneDeep(this.times); // Vueのレンダリングでvueインスタンス?になるのを防ぐため、複製しておく
// 件数分を指定の時間フォーマットにする
const items = tmp.filter((time, index) => index < this.options.num).sort((dateA, dateB) => {
let result = 0; // 結果
const timeA = Number(dateA.valueOf());
const timeB = Number(dateB.valueOf());
// 並び替え
if (this.options.sort === 'newest') {
// 新しい順
if (timeA < timeB) {
result = 1;
} else if (timeA > timeB) {
result = -1;
}
} else if (this.options.sort === 'oldest') {
// 古い順
if (timeA > timeB) {
result = 1;
} else if (timeA < timeB) {
result = -1;
}
}
return result;
});
// ソート順を反映する
return items.map(time => moment(time).format(this.options.format));
},
/**
* 期間の範囲を指定する
*
*/
range: function () {
const start = moment(this.options.range.start);
const end = moment(this.options.range.end);
// endは日付指定のみ(時間未指定)の場合、その日いっぱいを含む
if (end.isValid() && this.options.range.end.match(/^(\d{2,4}\s*\/)?\s*\d{1,2}\s*\/\s*\d{1,2}$/)) {
end.endOf('day');
}
// 入力された時間の範囲をミリ秒に変換する
// エラーだった場合は、
return {
start: {
date: start.isValid() ? Number(start.format('x')) : Number(moment().startOf('day').format('x')),
isValid: start.isValid()
},
end: {
date: end.isValid() ? Number(end.format('x')) : Number(moment().endOf('day').format('x')),
isValid: end.isValid()
}
}
},
/**
* 除外する期間、時間を計算する
*
*/
not: function () {
const result = this.options.not.map((item) => {
// 入力された日時を補完する
// 文字列からフォーマットを推測する
// 開始、終了のどちらかが未入力なら、それ以前、それ以降と判定する
// 日時がおかしい or フォーマットが不明な場合は無効にする
let start = item.start; // 除外開始日時
let end = item.end; // 除外終了日時
const startDate = moment(item.start);
let endDate = moment(item.end);
// 日時 or 時間のみを判定する
// 書式から判定する
let type = null;
let isValid = true;
let isInput = false;
// console.log(startDate.format('YYYY/M/D H:mm:ss.SSS'));
// console.log(endDate.format('YYYY/M/D H:mm:ss.SSS'));
if (item.start !== null && item.start !== '' && item.end !== null && item.end !== '') {
isInput = true;
// console.log('---');
// console.log(item.start.match(timeFormat), item.end.match(timeFormat));
// console.log(startDate.isValid(), endDate.isValid());
// console.log(startDate.isValid(), item.end.match(timeFormat));
if (item.start.match(timeFormat) && item.end.match(timeFormat)) {
// start, endが時間
type = 'interval';
start = item.start;
end = item.end;
} else if (startDate.isValid() && endDate.isValid()) {
// start, endが日時
type = 'period';
// 開始と終了をミリ秒で保持
start = Number(startDate.format('x'));
end = Number(endDate.endOf('day').format('x')); // その日いっぱいをNotにする
if (start >= end) {
isValid = false;
}
} else if (startDate.isValid() && item.end.match(timeFormat)) {
// startが日時、endが時間
// ex. 2018/1/1, 12:34
type = 'period';
// 開始と終了をミリ秒で保持
start = Number(startDate.format('x'));
// end = // 終了時間を計算する
endDate = startDate.clone();
// startDateとendを組み合わせて、終了時間を生成する
// →時間だけ反映する
const endTime = end.split(/:|\./i).map((num) => Number(num));
endDate.hours(endTime[0] | 0);
endDate.minutes(endTime[1] | 0);
endDate.seconds(endTime[2] | 0);
endDate.milliseconds(endTime[3] | 0);
end = Number(endDate.format('x'));
if (start >= end) {
isValid = false;
}
} else if (startDate.isValid() && item.end.match(dateFormat)) {
// startが日時、endが日付
// ex. 2018/1/1, 1/10
type = 'period';
// 開始と終了をミリ秒で保持
start = Number(startDate.format('x'));
// end = // 終了時間を計算する
end = Number(moment(`${startDate.format('YYYY')}/${item.end}`).endOf('day').format('x')); // その日いっぱいをNotにする
if (start >= end) {
isValid = false;
}
} else {
isValid = false;
}
}
// console.log('type', type, 'isValid', isValid, 'start', start, 'end', end);
return {
start,
end,
type,
isValid,
isInput
};
});
// console.log('not', result);
return result;
},
/**
* 結果をコピーできる文字列を取得
*
*/
copyAllResults: function () {
return this.results.join('\n');
}
},
watch: {
// 範囲が変更されたら生成しなおす
'range.start.date': function () {
this.generateTimes();
},
// 範囲が変更されたら生成しなおす
'range.end.date': function () {
this.generateTimes();
},
// 除く日時が更新されたら生成しなおす
'not': function () {
this.generateTimes();
}
}
});
new ClipboardJS('.copy-results');
Also see: Tab Triggers