hippie/source/screens/demo/examples/clock.liquid

281 lines
7.6 KiB
Text
Raw Normal View History

---
title: Clock
tags:
- demoExample
---
{% assign bodyClass = 'body_clock' -%}
{% layout 'hippie/simple.liquid' %}
{% block body %}
<header class="io pos_fix pin_top pin_right pin_left">
<button id="tglFormat" title="Toggle hour display">12-Stunden</button>
<button id="tglSections" title="Toggle sections">Abschnitte</button>
</header>
2026-03-01 10:50:43 +01:00
<main></main>
{% endblock %}
{% block script %}
<script>
2026-03-01 10:50:43 +01:00
// TODO: Kalenderwoche ergänzen
// TODO: Mondphase ergänzen
class HippieClock {
constructor(element, date, options) {
this.element = element;
this.date = this.getTime(date);
this.options = options || {
size: Math.floor(this.getSize().value * 0.9),
h24: true,
sections: true
};
this.parts = this.createContext(['bkg', 'hands']);
this.shapes = [];
this.init();
}
init() {
this.addRing('seconds', 1, 21, 60, `rgb(250, 216, 3)`);
this.addRing('minutes', .9, 46, 60, `rgb(242, 175, 19)`);
this.addRing('minutes', .8, 6, this.options.h24 ? 24 : 12, `rgb(211, 10, 81)`);
this.addRing('dotweek', .7, 32, 7, `rgb(142, 31, 104)`);
this.addRing('dotmonth', .6, 12, this.getTime().daysMonth, `rgb(39, 63, 139)`);
this.addRing('dotyear', .5, 256, 365, `rgb(60, 87, 154)`);
this.addRing('month', .4, 10, 12, `rgb(183, 224, 240)`);
this.resize();
window.addEventListener('resize', () => this.resize());
console.debug(this);
}
resize() {
this.parts.forEach(part => {
part.element.width = part.element.offsetWidth;
part.element.height = part.element.offsetHeight;
this.draw();
});
}
draw() {
// TODO: Nur geänderte Teile löschen
this.parts.forEach(part => {
part.context.clearRect(0, 0, part.element.width, part.element.height);
});
let ctx = undefined;
this.shapes
.filter(item => item.type === 'circle')
.forEach((shape) => {
const radius = this.toPixelSize(shape.radius) / 2;
ctx = this.parts[0].context;
ctx.fillStyle = shape.color;
ctx.beginPath();
ctx.arc(
this.toPixelX(shape.center),
this.toPixelY(shape.center),
radius,
0,
Math.PI * 2
);
ctx.fill();
});
this.shapes
.filter(item => item.type === 'ring')
.forEach((shape) => {
if (this.options.sections) {
const outerRadius = this.toPixelSize(shape.radius) / 2;
const innerRadius = this.toPixelSize(shape.radius) / 2 - shape.stroke;
ctx = this.parts[0].context;
for (let i = 0; i < shape.max; i++) {
const angle = (i * (360 / shape.max) - 90) * (Math.PI / 180); // -90 to start at top
const outerX = this.toPixelX(shape.center) + outerRadius * Math.cos(angle);
const outerY = this.toPixelY(shape.center) + outerRadius * Math.sin(angle);
const innerX = this.toPixelX(shape.center) + innerRadius * Math.cos(angle);
const innerY = this.toPixelY(shape.center) + innerRadius * Math.sin(angle);
ctx.strokeStyle = 'black';
// TODO: Stärke an shape.max orientieren
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(outerX, outerY);
ctx.lineTo(innerX, innerY);
ctx.stroke();
}
}
const radius = this.toPixelSize(shape.radius) / 2 - shape.stroke / 2;
const start = -0.5 * Math.PI; // Start at the top
const angle = start + (2 * Math.PI * (shape.angle / shape.max));
ctx = this.parts[1].context;
ctx.strokeStyle = shape.color;
ctx.lineWidth = shape.stroke;
ctx.beginPath();
ctx.arc(
this.toPixelX(shape.center),
this.toPixelY(shape.center),
radius,
start,
angle
);
ctx.stroke();
});
}
update() {
const second = this.getTime().second;
const minute = this.getTime().minute;
const hour = this.getTime().hour;
this.updateShape('seconds', (second === 0) ? 60 : second);
this.updateShape('minutes', (minute === 0) ? 60 : minute);
this.updateShape('hours', this.options.h24 ? hour : hour % 12, this.options.h24 ? 24 : 12);
this.updateShape('dotweek', this.getTime().dayWeek);
this.updateShape('dotmonth', this.getTime().dayMonth, this.getTime().daysMonth);
this.updateShape('dotyear', this.getTime().dayYear);
this.updateShape('month', this.getTime().month);
this.draw();
}
toPixelX(number) {
return number * this.parts[0].element.width;
}
toPixelY(number) {
return number * this.parts[0].element.height;
}
toPixelSize(number) {
return number * Math.min(this.parts[0].element.width, this.parts[0].element.height);
}
// TODO: Parameter für Wochenstart ergänzen
getNumericWeekday(date) {
const weekday = date.getDay(); // 0 (Sunday) to 6 (Saturday)
return (weekday === 0) ? 7 : weekday;
}
getNumericYearDay(date) {
const start = new Date(date.getFullYear(), 0, 0);
return Math.floor((date - start) / 86400000);
}
daysInMonth(month, year) {
return new Date(year, month, 0).getDate();
}
getSize() {
const height = window.innerHeight;
const width = window.innerWidth;
return {
value: Math.min(height, width),
smaller: height < width ? 'height' : 'width'
};
}
getTime(date) {
this.date = date || new Date();
return {
second: this.date.getSeconds(),
minute: this.date.getMinutes(),
hour: this.date.getHours(),
dayWeek: this.getNumericWeekday(this.date),
dayMonth: this.date.getDate(),
dayYear: this.getNumericYearDay(this.date),
month: this.date.getMonth() + 1, // Get current month (0-11)
daysMonth: this.daysInMonth(this.date.getMonth() + 1, this.date.getFullYear())
};
}
createContext(names) {
let parts = [];
const wrap = document.createElement('div');
wrap.style.position = 'relative';
wrap.style.height = this.options.size + 'px';
wrap.style.width = this.options.size + 'px';
names.forEach(name => {
const canvas = document.createElement('canvas');
canvas.style.position = 'absolute';
canvas.style.top = '0px';
canvas.style.left = '0px';
canvas.style.height = '100%';
canvas.style.width = '100%';
canvas.height = canvas.offsetHeight;
canvas.width = canvas.offsetWidth;
wrap.appendChild(canvas);
parts.push({name: name, element: canvas, context: canvas.getContext('2d')});
});
this.element.appendChild(wrap);
return parts;
}
updateShape(id, angle, max) {
const shape = this.shapes.find(s => s.id === id);
if (shape) {
shape.angle = angle;
if (max) shape.max = max;
}
}
updateOptions(changes) {
this.options = {...this.options, ...changes};
}
addCircle(id, center, radius, color = `rgba(0, 0, 0, .1)`) {
this.shapes.push({type: 'circle', id, center, radius, color});
}
addRing(id, radius, angle, max, color = 'black', center = .5, stroke = 16) {
this.shapes.push({type: 'ring', id, radius, angle, max, color, center, stroke});
}
addSection(id, radius, color = 'black', center = .5, stroke = 1) {
this.shapes.push({type: 'section', id, radius, color, center, stroke});
}
}
const container = document.querySelector('#clock main');
const clock = new HippieClock(container);
clock.addCircle('base', .5, 1);
clock.draw();
setInterval(() => {
clock.update();
}, 1000);
// TODO: Aktionen gehören quasi zu HippieClock
document.getElementById('tglFormat').addEventListener('click', () => {
if (clock) {
clock.updateOptions({h24: !clock.options.h24})
document.getElementById('tglFormat').textContent = clock.options.h24 ? '12-Stunden' : '24-Stunden';
} else {
console.log('No clock available');
}
});
document.getElementById('tglSections').addEventListener('click', () => {
if (clock) {
clock.updateOptions({sections: !clock.options.sections})
} else {
console.log('No clock available');
}
});
</script>
{% endblock %}