はじめに
朝と夜の区別が付きにくくなった方に向けての時計を探していたのですが、買うと意外と高いのでChatGPTを使って作りました。
ブラウザ表示できれば良いので、安いタブレットをメルカリで買って表示させています。
完成品
↓ クリック ↓
仕様とソース
仕様
- 縦横切替対応
- 縦画面:上下にボックス2つ
- 横画面:左右にボックス2つ(左ボックスの方が少し広くする)
- 1つ目のボックス:日時文字列の表示
- 年 / 日付 / 12時間の時刻 の3行
- 年:yyyy年
- 日付:M月d日(aaa)
- 時刻:{午前 or 午後} H:mm(12時間表示)
- ボックスいっぱいに表示
- 内容全体がボックス内で 縦横中央
- メリハリがつくように各項目のサイズ変える
- 2つ目のボックス:画像表示
- 月ごとの一般的時間帯で朝/日中/夕方/夜の画像を切替表示
- 切替タイミングと取得失敗時のみ再読み込み(キャッシュ回避)
- ボックスいっぱいに表示
- ボックス内で 縦横中央配置
- 元の画像の縦横比は変えない
- 技術
- 古いAndroid対応
- Android 4.2、低スペックでも動作
- スクロールバー非表示
- 軽量・シンプル
- 焼き付き防止
ソース
HTML
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>朝と夜が分かりやすい時計</title>
<style>
html, body {
margin:0; padding:0;
height:104%; /* アドレスバー隠し認識 */
width:100%;
overflow:hidden;
background:#222;
color:#aaa;
font-family:sans-serif;
}
#container {
width:100%; height:100%;
overflow:hidden;
text-align:center;
}
.box {
background:#222;
position:relative;
overflow:hidden;
transform:translateZ(0);
}
#timeLine span{
display:inline-block;
}
#timeText{
margin-left:0.1em;
}.time-line {
position:absolute;
white-space:nowrap;
text-align:center;
display:block;
font-weight:bold;
}
#imageBox img {
position:absolute;
display:none;
}
</style>
</head>
<body>
<div id="container">
<div class="box" id="clockBox">
<span id="yearLine" class="time-line">--</span>
<span id="dateLine" class="time-line">--</span>
<span id="timeLine" class="time-line">
<span id="ampmText">--</span>
<span id="timeText">--</span>
</span>
</div>
<div class="box" id="imageBox">
<img id="morning" src="morning.png" alt="朝">
<img id="noon" src="noon.png" alt="日中">
<img id="evening" src="evening.png" alt="夕方">
<img id="night" src="night.png" alt="夜">
</div>
</div>
<script>
(function(){
var clockBox = document.getElementById('clockBox');
var yearLine = document.getElementById('yearLine');
var dateLine = document.getElementById('dateLine');
var timeLine = document.getElementById('timeLine');
var ampmText = document.getElementById('ampmText');
var timeText = document.getElementById('timeText');
var imageBox = document.getElementById('imageBox');
var images = [
document.getElementById('morning'),
document.getElementById('noon'),
document.getElementById('evening'),
document.getElementById('night')
];
var currentIndex = -1;
var imageLoadError = false;
var prevHour = -1;
var burnX = 0;
var burnY = 0;
// レイアウト調整
function resizeBoxes(){
var winW = window.innerWidth;
var winH = window.innerHeight || document.documentElement.clientHeight;
var isVertical = winH > winW;
if(isVertical){
clockBox.style.width = '98%';
imageBox.style.width = '98%';
clockBox.style.height = px(winH * 0.48);
imageBox.style.height = px(winH * 0.48);
clockBox.style.float = 'none';
imageBox.style.float = 'none';
} else {
var cW = winW * 0.54;
var iW = winW * 0.44;
clockBox.style.width = px(cW);
imageBox.style.width = px(iW);
clockBox.style.height = px(winH);
imageBox.style.height = px(winH);
clockBox.style.float = 'left';
imageBox.style.float = 'left';
}
clockBox.style.margin='1% auto';
imageBox.style.margin='1% auto';
}
// レイアウト更新まとめ
function updateLayout(){
var boxW = clockBox.offsetWidth;
var boxH = clockBox.offsetHeight;
// フォントサイズ比率
var FONT_SIZE_RATE_YEAR = 0.8;
var FONT_SIZE_RATE_DATE = 1.0;
var FONT_SIZE_RATE_SPACE = 0.1;
var FONT_SIZE_RATE_AMPM = 0.8;
var FONT_SIZE_RATE_TIME = 1.6;
var FONT_PADDING_PX_BOTH = 40;
// 高さ基準で仮フォントサイズを決定
var base = boxH /
(
FONT_SIZE_RATE_YEAR + FONT_SIZE_RATE_DATE + FONT_SIZE_RATE_SPACE +
Math.max(FONT_SIZE_RATE_AMPM, FONT_SIZE_RATE_TIME)
);
var fYear = base * FONT_SIZE_RATE_YEAR;
var fDate = base * FONT_SIZE_RATE_DATE;
var fAMPM = base * FONT_SIZE_RATE_AMPM;
var fTime = base * FONT_SIZE_RATE_TIME;
// 仮フォントサイズセット
yearLine.style.fontSize = px(fYear);
dateLine.style.fontSize = px(fDate);
ampmText.style.fontSize = px(fAMPM);
timeText.style.fontSize = px(fTime);
// 横幅を測るための scale 計算
var yW = yearLine.offsetWidth;
var dW = dateLine.offsetWidth;
var tW = timeLine.offsetWidth;
var scaleW = (boxW - FONT_PADDING_PX_BOTH) / Math.max(yW, dW, tW);
scaleW = Math.min(scaleW, 1); // 大きくはしない
// 縦方向も確認
var yH = yearLine.offsetHeight;
var dH = dateLine.offsetHeight;
var tH = timeLine.offsetHeight;
var totalH = yH + dH + tH + (yH * FONT_SIZE_RATE_SPACE);
var scaleH = boxH / totalH;
scaleH = Math.min(scaleH, 1);
// 最終縮小率で再セット
var scale = Math.min(scaleH, scaleW);
yearLine.style.fontSize = px(fYear * scale);
dateLine.style.fontSize = px(fDate * scale);
ampmText.style.fontSize = px(fAMPM * scale);
timeText.style.fontSize = px(fTime * scale);
// 時刻:中央配置
yH = yearLine.offsetHeight;
dH = dateLine.offsetHeight;
tH = timeLine.offsetHeight;
var spaceH = yH * FONT_SIZE_RATE_SPACE;
totalH = yH + dH + spaceH + tH;
var top = (boxH - totalH) / 2;
yearLine.style.top = px(top);
dateLine.style.top = px(top + yH);
timeLine.style.top = px(top + yH + dH + spaceH);
yW = yearLine.offsetWidth;
dW = dateLine.offsetWidth;
tW = timeLine.offsetWidth;
yearLine.style.left = px((boxW - yW) / 2);
dateLine.style.left = px((boxW - dW) / 2);
timeLine.style.left = px((boxW - tW) / 2);
// 画像:中央配置
var imgBoxW = imageBox.offsetWidth;
var imgBoxH = imageBox.offsetHeight;
var imgBoxMinW = imgBoxW - 20; // padding10px両側
var imgBoxMinH = imgBoxH - 20; // padding10px両側
var img = images[currentIndex];
if(img && img.complete) {
// 元画像サイズ
var naturalW = img.naturalWidth || img.width || 1;
var naturalH = img.naturalHeight || img.height || 1;
// 縦横比維持してボックス内最大
var imgScale = Math.min(imgBoxMinW / naturalW, imgBoxMinH / naturalH);
var displayW = naturalW * imgScale;
var displayH = naturalH * imgScale;
img.style.width = px(displayW);
img.style.height = px(displayH);
// 中央に配置
img.style.left = px((imgBoxW - displayW) / 2);
img.style.top = px((imgBoxH - displayH) / 2);
}
}
// 時刻更新+画像切替
function updateDisplay(){
// 時刻計算
var now = new Date();
var year = now.getFullYear();
var month = now.getMonth()+1;
var day = now.getDate();
var week = ['日','月','火','水','木','金','土'][now.getDay()];
var hour =now.getHours();
var min = (now.getMinutes()<10?'0':'')+now.getMinutes();
var ampm = hour<12?'午前':'午後';
var hour12 = hour%12;
// if(hour12===0) hour12=12; // テレビ番組表にあわせる12:00→00:00
// 時刻表示
yearLine.textContent = year+'年';
dateLine.textContent = month+'月'+day+'日('+week+')';
ampmText.textContent = ampm;
timeText.textContent = hour12+':'+min;
// 月ごとの時間帯設定(朝 < 日中 < 夕方 < 夜)
var monthTable=[
[5,8,16,19], [5,8,16,19], [5,8,16,19],
[5,8,16,19], [5,8,18,19], [5,8,16,19],
[5,8,16,19], [5,8,16,19], [5,8,16,19],
[5,9,16,19], [5,8,16,19], [5,8,16,19]
];
var mt = monthTable[month-1];
var newIndex = (hour >= mt[3] || hour < mt[0]) ? 3 // 夜
: (hour >= mt[2]) ? 2 // 夕方
: (hour >= mt[1]) ? 1 // 日中
: 0; // 朝
if(newIndex!==currentIndex || imageLoadError){
if(currentIndex>=0){
images[currentIndex].style.display='none';
}
var img = images[newIndex];
img.onload = function(){
imageLoadError=false;
updateLayout();
};
img.onerror=function(){
imageLoadError=true;
};
img.src = img.src.split('?')[0]+'?t='+now.getTime();
img.style.display='block';
currentIndex=newIndex;
}
// 時が変わるタイミング
if(hour !== prevHour){
// レイアウトの再計算
updateLayout();
// 焼き付き防止
burnX += Math.random() < 0.5 ? -1 : 1;
burnY += Math.random() < 0.5 ? -1 : 1;
burnX = Math.min(Math.max(burnX, -3), 3);
burnY = Math.min(Math.max(burnY, -3), 3);
clockBox.style.transform = translate(burnX, burnY);
imageBox.style.transform = translate(burnX, burnY);
prevHour = hour;
}
}
// ヘルパー関数
function px(val){ return val+"px"; }
function translate(x, y){ return "translate("+x+"px,"+y+"px)"; }
// 初期処理
resizeBoxes();
updateDisplay();
// resize
var resizeTimer = null;
window.addEventListener('resize',function(){
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function(){
resizeBoxes();
updateLayout();
}, 200);
});
// 1分
function minuteLoop(){
updateDisplay();
setTimeout(minuteLoop,60000 - (Date.now() % 60000));
}
setTimeout(minuteLoop,60000 - (Date.now() % 60000));
})();
</script>
</body>
</html>
参考資料
- なし
おわりに
ご意見いただければ幸いです。