Skip to content

Instantly share code, notes, and snippets.

@bgh1234554
Forked from kirinelf/clock.html
Last active August 20, 2025 09:07
Show Gist options
  • Save bgh1234554/5dc18afef12b34515a036c78bb66a857 to your computer and use it in GitHub Desktop.
Save bgh1234554/5dc18afef12b34515a036c78bb66a857 to your computer and use it in GitHub Desktop.
OBS Studio: A HTML page for showing current date and time in the video with the analog clock you can toggle
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>A simple clock</title>
<style>
:root{
/* design of a clock*/
--diameter: 820px;
--tick-color: rgba(255,255,255,.85);
--tick-major-color: #ffffff;
--hand-hour-color: #ffffff;
--hand-minute-color: #ffffff;
--hand-second-color: #ff0000;
}
#clock-root{ position:relative; width:var(--diameter); height:var(--diameter); margin:0 auto; }
#analog{ position:absolute; inset:0; display:none; pointer-events:none; } /* 토글 off면 숨김 */
#analog.on{ display:block; }
#analog img{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; z-index:0; }
#analog svg{ position:absolute; inset:0; z-index:1; }
/* Digital Clock in the middle of the clock */
#output.center-in-clock{ position:absolute; left:50%; top:50%; transform:translate(-50%,-50%); z-index:2; }
.hand.hour { stroke: var(--hand-hour-color); stroke-width:17; stroke-linecap:round; }
.hand.minute { stroke: var(--hand-minute-color); stroke-width:13; stroke-linecap:round; }
.hand.second { stroke: var(--hand-second-color); stroke-width:7; stroke-linecap:round; }
.tick { stroke: var(--tick-color); stroke-width:6; opacity:.9; }
.tick.major { stroke: var(--tick-major-color); stroke-width:9; }
.hub{ fill:#fff; }
/* automatic shrink for small screen */
@media (max-width: 700px){ :root{ --diameter: 88vw; } }
#bg-solid{ position:absolute; inset:0; z-index:0; background: var(--bg-solid, transparent); }
/* Control Panel for Settings */
#controls{
position: fixed; right: 16px; bottom: 16px;
background: rgba(0,0,0,.6); color:#fff;
font: 14px/1.4 system-ui, -apple-system, Segoe UI, Roboto, "Noto Sans KR", sans-serif;
backdrop-filter: blur(6px);
border:1px solid #ffffff30; border-radius:12px; padding:12px; z-index:999;
}
#controls .row{ display:flex; gap:8px; align-items:center; flex-wrap:wrap; margin:6px 0; }
#controls input[type="number"]{ width: 92px; }
#controls input[type="range"]{ width: 120px; }
#controls hr{ border:none; border-top:1px solid #ffffff30; margin:8px 0; }
#controls button{ cursor:pointer; }
#controls.hidden{ display:none; }
#showPanelBtn{ position:fixed; right:16px; bottom:16px; z-index:998; display:none; }
</style>
</head>
<body translate="no">
<div id="clock-root">
<!-- Analog Overlay -->
<div id="analog">
<div id="bg-solid"></div>
<img id="clock-bg" alt="Clock background" style="display:none">
<svg id="dial" viewBox="0 0 1000 1000" preserveAspectRatio="xMidYMid meet">
<g id="tickContainer"></g>
<line id="hourHand" class="hand hour" x1="500" y1="500" x2="500" y2="320" />
<line id="minuteHand" class="hand minute" x1="500" y1="500" x2="500" y2="220" />
<line id="secondHand" class="hand second" x1="500" y1="500" x2="500" y2="170" />
<circle class="hub" cx="500" cy="500" r="14"></circle>
</svg>
</div>
<!-- Previous Digital Clock design -->
<div id="output"
style="display: inline-block;
font-family: Ubuntu, 'HUMidnight140';
font-size: 75px;
text-align: center;
color: white;
border-radius: 10px;
padding: 10px;
background-color: rgba(0, 0, 0, 0.75);">
</div>
</div>
<div id="controls">
<div class="row">
<label><input type="checkbox" id="toggleAnalog" checked="true"> Toggle Analog Overlay</label>
</div>
<div class="row">
<label>Diameter(px)
<input type="number" id="diameter" min="300" max="1500" step="10" value="820">
</label>
</div>
<div class="row">
<label>Hourhand <input type="color" id="colorHour" value="#ffffff"></label>
<label>Minutehand <input type="color" id="colorMinute" value="#ffffff"></label>
<label>Secondhand <input type="color" id="colorSecond" value="#ff0000"></label>
</div>
<div class="row">
<label>colorTickMajor <input type="color" id="colorTickMajor" value="#ffffff"></label>
<label>colorTickMinor <input type="color" id="colorTick" value="#e5e7eb"></label>
</div>
<hr>
<div class="row">
<label>Background <input type="color" id="bgSolidColor" value="#000000"></label>
<label>Alpha <input type="range" id="bgSolidAlpha" min="0" max="1" step="0.01" value="1"></label>
<button type="button" id="applyBgToPage">Apply to the background</button>
</div>
<div class="row">
<label>Background Image <input type="file" id="bgFile" accept="image/*"></label>
<button type="button" id="clearBgImage"> Remove Image </button>
</div>
<div class="row">
<button type="button" id="hidePanel">Hide Panel</button>
<button type="button" id="copyLink">Copy Link</button>
</div>
</div>
<button id="showPanelBtn" type="button">Show Panel</button>
<script src='https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment-with-locales.min.js'></script>
<script>
/* used for Korean clock since I'm from South Korea */
moment.locale('en');
// https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
var urlParams;
(function () {
var match,
pl = /\+/g, // Regex for replacing addition symbol with a space
search = /([^&=]+)=?([^&]*)/g,
decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); },
query = window.location.search.substring(1);
urlParams = {};
while (match = search.exec(query))
urlParams[decode(match[1])] = decode(match[2]);
})();
var output = document.getElementById("output");
if (urlParams["style"]) output.setAttribute("style", urlParams["style"]);
if (urlParams["bodyStyle"]) document.body.setAttribute("style", urlParams["bodyStyle"]);
// ===== Analog Toggle option(initial value) =====
const truthy = v => /^(1|true|yes|on)$/i.test((v||'').trim());
let analogOn = (urlParams["analog"] === undefined)
? true // The analog clock is initially on
: truthy(urlParams["analog"]); // analog=1/true/on → ON,
const size = urlParams["diameter"] || urlParams["size"];
if (size) {
const v = /px|vw|vh|%/.test(size) ? size : (size+'px');
document.documentElement.style.setProperty('--diameter', v);
}
// Colour Customise (?handHourColor=%23ff0000 처럼 #은 %23로)
const colorMap = {
tickColor: '--tick-color',
tickMajorColor: '--tick-major-color',
handHourColor: '--hand-hour-color',
handMinuteColor: '--hand-minute-color',
handSecondColor: '--hand-second-color'
};
Object.keys(colorMap).forEach(k=>{
if (urlParams[k]) document.documentElement.style.setProperty(colorMap[k], urlParams[k]);
});
// ===== Drawing Analog Clock =====
function polar(len, deg) {
const rad = (deg-90) * Math.PI/180;
return { x: 500 + len*Math.cos(rad), y: 500 + len*Math.sin(rad) };
}
function setupAnalog() {
const img = document.getElementById('clock-bg');
const bg = urlParams["bg"]; // 기본 없음. 있으면만 로드
// Background Image
img.style.display = 'none';
img.onload = () => { img.style.display = 'block'; };
img.onerror = () => { img.style.display = 'none'; img.removeAttribute('src'); };
if (bg) img.src = bg;
else { img.removeAttribute('src'); }
// Drawing Ticks
const ticks = document.getElementById('tickContainer');
ticks.innerHTML = '';
const outer = 480, minor = outer-35, major = outer-70;
for (let i=0; i<60; i++){
const angle = i*6;
const isMajor = i%5===0;
const p1 = polar(outer, angle);
const p2 = polar(isMajor ? major : minor, angle);
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line.setAttribute('x1', p1.x); line.setAttribute('y1', p1.y);
line.setAttribute('x2', p2.x); line.setAttribute('y2', p2.y);
line.setAttribute('class', 'tick' + (isMajor ? ' major' : ''));
ticks.appendChild(line);
}
// Digital Clock at the center of the Analog Clock
document.getElementById('analog').classList.add('on');
document.getElementById('output').classList.add('center-in-clock');
}
function updateAnalog() {
const now = new Date();
const s = now.getSeconds() //+ now.getMilliseconds()/1000;
const m = now.getMinutes() + s/60;
const h = (now.getHours()%12) + m/60;
const sDeg = s*6; // 360/60
const mDeg = m*6; // 360/60
const hDeg = h*30; // 360/12
function setHand(id, length, deg){
const p = polar(length, deg);
const el = document.getElementById(id);
el.setAttribute('x2', p.x);
el.setAttribute('y2', p.y);
}
setHand('secondHand', 330, sDeg);
setHand('minuteHand', 300, mDeg);
setHand('hourHand', 220, hDeg);
}
if (analogOn) setupAnalog();
function tick() {
// Update Digital Clock
output.innerText = moment().format(urlParams["format"] || 'YYYY-MM-DD (ddd) HH:mm:ss');
// Update Analog Clock
if (analogOn) updateAnalog();
const now = Date.now();
const delay = 1000 - (now % 1000);
setTimeout(tick, delay);
}
tick();
// ===== Control Panel =====
const $ = s => document.querySelector(s);
const elToggle = $('#toggleAnalog');
const elHour = $('#colorHour'), elMinute = $('#colorMinute'), elSecond = $('#colorSecond');
const elTickMajor = $('#colorTickMajor'), elTick = $('#colorTick');
const elDiameter = $('#diameter');
const elBgSolidColor = $('#bgSolidColor'), elBgSolidAlpha = $('#bgSolidAlpha');
const elBgFile = $('#bgFile'), elClearBg = $('#clearBgImage');
const elApplyPageBg = $('#applyBgToPage');
const elPanel = $('#controls'), elHidePanel = $('#hidePanel'), elShowPanel = $('#showPanelBtn');
function setVar(name, value){ document.documentElement.style.setProperty(name, value); }
function hexToRgb(hex){
hex = (hex||'').replace('#','');
if (hex.length===3) hex = hex.split('').map(c=>c+c).join('');
const num = parseInt(hex||'000000',16);
return { r:(num>>16)&255, g:(num>>8)&255, b:num&255 };
}
function rgbaFrom(hex, a){
const {r,g,b} = hexToRgb(hex);
return `rgba(${r},${g},${b},${isNaN(a)?1:a})`;
}
function applyColors(){
setVar('--hand-hour-color', elHour.value);
setVar('--hand-minute-color', elMinute.value);
setVar('--hand-second-color', elSecond.value);
setVar('--tick-major-color', elTickMajor.value);
setVar('--tick-color', elTick.value);
}
function applyDiameter(){
const v = elDiameter.value ? (elDiameter.value+'px') : '820px';
setVar('--diameter', v);
}
function applyBgSolid(){
const rgba = rgbaFrom(elBgSolidColor.value, parseFloat(elBgSolidAlpha.value));
setVar('--bg-solid', rgba);
document.getElementById('bg-solid').style.display = 'block';
}
function loadBgFile(){
const f = elBgFile.files && elBgFile.files[0];
if (!f) return;
const url = URL.createObjectURL(f);
const img = document.getElementById('clock-bg');
img.style.display = 'none';
img.onload = () => { img.style.display = 'block'; };
img.onerror = () => { img.style.display = 'none'; img.removeAttribute('src'); };
img.src = url;
}
function clearBg(){
const img = document.getElementById('clock-bg');
img.style.display = 'none';
img.removeAttribute('src');
}
elToggle.addEventListener('change', ()=>{
analogOn = elToggle.checked;
if (analogOn) setupAnalog();
else{
document.getElementById('analog').classList.remove('on');
document.getElementById('output').classList.remove('center-in-clock');
}
});
[elHour, elMinute, elSecond, elTick, elTickMajor].forEach(el=> el.addEventListener('input', applyColors));
elDiameter.addEventListener('input', applyDiameter);
elBgSolidColor.addEventListener('input', applyBgSolid);
elBgSolidAlpha.addEventListener('input', applyBgSolid);
elBgFile.addEventListener('change', loadBgFile);
elClearBg.addEventListener('click', clearBg);
elApplyPageBg.addEventListener('click', ()=>{
// Apply clock background on page background as well
const rgbaCol = getComputedStyle(document.documentElement).getPropertyValue('--bg-solid') || rgbaFrom(elBgSolidColor.value, elBgSolidAlpha.value);
document.body.style.background = (rgbaCol||elBgSolidColor.value).trim();
});
// Panel show/hide
elHidePanel.addEventListener('click', ()=>{ elPanel.classList.add('hidden'); elShowPanel.style.display='block'; });
elShowPanel.addEventListener('click', ()=>{ elPanel.classList.remove('hidden'); elShowPanel.style.display='none'; });
// Initial synchronize
(function initUI(){
elToggle.checked = analogOn;
if (analogOn) setupAnalog();
// URL Parameter → Control Value
if (urlParams["diameter"]) elDiameter.value = parseInt(urlParams["diameter"]);
if (urlParams["handHourColor"]) elHour.value = urlParams["handHourColor"];
if (urlParams["handMinuteColor"]) elMinute.value = urlParams["handMinuteColor"];
if (urlParams["handSecondColor"]) elSecond.value = urlParams["handSecondColor"];
if (urlParams["tickColor"]) elTick.value = urlParams["tickColor"];
if (urlParams["tickMajorColor"]) elTickMajor.value = urlParams["tickMajorColor"];
applyColors(); applyDiameter(); applyBgSolid();
})();
// Copy Link of current setting
document.getElementById('copyLink').addEventListener('click', ()=>{
const params = new URLSearchParams();
params.set('analog', elToggle.checked ? '1' : '0');
params.set('diameter', elDiameter.value);
params.set('handHourColor', elHour.value);
params.set('handMinuteColor', elMinute.value);
params.set('handSecondColor', elSecond.value);
params.set('tickColor', elTick.value);
params.set('tickMajorColor', elTickMajor.value);
const link = location.origin + location.pathname + '?' + params.toString();
navigator.clipboard.writeText(link).then(()=> alert('현재 설정 링크를 복사했어!'));
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment