-
-
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 + WITH THE ANALOG CLOCK
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{ | |
| --diameter: 820px; /* 시계 지름 */ | |
| --tick-color: rgba(255,255,255,.85); /* 작은 눈금색 */ | |
| --tick-major-color: #ffffff; /* 12개 큰 눈금색 */ | |
| --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; } | |
| /* 디지털 시간 글자를 시계 한가운데로 */ | |
| #output.center-in-clock{ position:absolute; left:50%; top:50%; transform:translate(-50%,-50%); z-index:2; } | |
| /* SVG 스타일 */ | |
| .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; } | |
| /* 화면이 좁으면 자동 축소 */ | |
| @media (max-width: 700px){ :root{ --diameter: 88vw; } } | |
| /* 아날로그 배경(단색) 레이어 */ | |
| #bg-solid{ position:absolute; inset:0; z-index:0; background: var(--bg-solid, transparent); } | |
| /* 컨트롤 패널 */ | |
| #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"> | |
| <!-- 아날로그 시계 오버레이 --> | |
| <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> | |
| <!-- 시침/분침/초침 (기준점: 500,500) --> | |
| <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> | |
| <!-- 기존 디지털 현재시간 --> | |
| <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"> 아날로그 오버레이 켜기</label> | |
| </div> | |
| <div class="row"> | |
| <label>지름(px) | |
| <input type="number" id="diameter" min="300" max="1500" step="10" value="820"> | |
| </label> | |
| </div> | |
| <div class="row"> | |
| <label>시침 <input type="color" id="colorHour" value="#ffffff"></label> | |
| <label>분침 <input type="color" id="colorMinute" value="#ffffff"></label> | |
| <label>초침 <input type="color" id="colorSecond" value="#ff0000"></label> | |
| </div> | |
| <div class="row"> | |
| <label>큰 눈금 <input type="color" id="colorTickMajor" value="#ffffff"></label> | |
| <label>작은 눈금 <input type="color" id="colorTick" value="#e5e7eb"></label> | |
| </div> | |
| <hr> | |
| <div class="row"> | |
| <label>배경색(크로마) <input type="color" id="bgSolidColor" value="#000000"></label> | |
| <label>불투명도 <input type="range" id="bgSolidAlpha" min="0" max="1" step="0.01" value="1"></label> | |
| <button type="button" id="applyBgToPage">페이지 배경에 적용</button> | |
| </div> | |
| <div class="row"> | |
| <label>배경 이미지 <input type="file" id="bgFile" accept="image/*"></label> | |
| <button type="button" id="clearBgImage">이미지 제거</button> | |
| </div> | |
| <div class="row"> | |
| <button type="button" id="hidePanel">패널 숨기기</button> | |
| <button type="button" id="copyLink">링크 복사</button> | |
| </div> | |
| </div> | |
| <button id="showPanelBtn" type="button">패널 표시</button> | |
| <script src='https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment-with-locales.min.js'></script> | |
| <script> | |
| moment.locale('ko'); | |
| // 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"]); | |
| // ===== 아날로그 토글/옵션(초기값) ===== | |
| const truthy = v => /^(1|true|yes|on)$/i.test((v||'').trim()); | |
| let analogOn = (urlParams["analog"] === undefined) | |
| ? true // 쿼리파라미터가 없으면 기본 ON | |
| : truthy(urlParams["analog"]); // analog=1/true/on → ON, 그 외는 OFF | |
| const size = urlParams["diameter"] || urlParams["size"]; | |
| if (size) { | |
| const v = /px|vw|vh|%/.test(size) ? size : (size+'px'); | |
| document.documentElement.style.setProperty('--diameter', v); | |
| } | |
| // 색상 커스터마이즈 (?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]); | |
| }); | |
| // ===== 아날로그 시계 그리기 ===== | |
| function polar(len, deg) { | |
| // SVG 기준(우측=0°), 우리는 12시를 0°처럼 보이게 -90° | |
| const rad = (deg-90) * Math.PI/180; | |
| return { x: 500 + len*Math.cos(rad), y: 500 + len*Math.sin(rad) }; | |
| } | |
| function setupAnalog() { | |
| // (추가) 배경 이미지 요소 & URL 파라미터 | |
| const img = document.getElementById('clock-bg'); | |
| const bg = urlParams["bg"]; // 기본 없음. 있으면만 로드 | |
| // 배경 이미지 | |
| // 먼저 숨기고, 성공하면 보이기 | |
| 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'); } // 없으면 아예 src 제거 | |
| // 눈금 60개 생성 (큰 눈금은 12개) | |
| 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); | |
| } | |
| // 보이기 + 디지털을 중심으로 | |
| 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(); | |
| // var c; | |
| // setInterval( | |
| // c = function() { | |
| // output.innerText = moment().format(urlParams["format"] || 'YYYY-MM-DD (ddd) HH:mm:ss'); | |
| // if (analogOn) updateAnalog(); | |
| // }, 1000); | |
| // c(); | |
| function tick() { | |
| // 디지털 표기 갱신 | |
| output.innerText = moment().format(urlParams["format"] || 'YYYY-MM-DD (ddd) HH:mm:ss'); | |
| // 아날로그 갱신 | |
| if (analogOn) updateAnalog(); | |
| // 다음 초의 0ms에 맞춰 스케줄 | |
| const now = Date.now(); | |
| const delay = 1000 - (now % 1000); | |
| setTimeout(tick, delay); | |
| } | |
| tick(); // 시작 | |
| // ===== 컨트롤 패널 ===== | |
| 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', ()=>{ | |
| // 단색 배경을 페이지 배경에도 적용 (OBS 크로마키용) | |
| const rgbaCol = getComputedStyle(document.documentElement).getPropertyValue('--bg-solid') || rgbaFrom(elBgSolidColor.value, elBgSolidAlpha.value); | |
| document.body.style.background = (rgbaCol||elBgSolidColor.value).trim(); | |
| }); | |
| // 패널 show/hide | |
| elHidePanel.addEventListener('click', ()=>{ elPanel.classList.add('hidden'); elShowPanel.style.display='block'; }); | |
| elShowPanel.addEventListener('click', ()=>{ elPanel.classList.remove('hidden'); elShowPanel.style.display='none'; }); | |
| // 초기 동기화 | |
| (function initUI(){ | |
| elToggle.checked = analogOn; | |
| if (analogOn) setupAnalog(); | |
| // URL 파라미터 → 컨트롤 값 | |
| 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(); | |
| })(); | |
| // 현재 설정으로 링크 만들기(공유용) | |
| 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