-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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