Buat Tampilan Sendiri
Buat tampilan meter sendiri dengan HTML, CSS, dan JavaScript. Setiap tampilan bisa dipakai dengan sumber apa pun, dari baterai sampai angka yang Anda kirim sendiri.
Mulai Cepat
- Buat tampilan: Di Pengaturan, klik Buat Tampilan… (Option-Command-N). Multimeter membuat folder berisi file awal yang sudah memakai data versi 2.
- Edit file: Multimeter menampilkan folder itu di Finder. Edit file-filenya dengan editor kode apa pun.
- Lihat perubahan langsung: Multimeter memuat ulang tampilan Anda setiap kali Anda menyimpan. Coba dengan berbagai sumber di Tambah Meter… atau Sesuaikan Meter Ini….
- Bagikan: Kompres folder di Finder, ganti nama .zip menjadi
.multimeter, lalu kirim ke galeri. Siapa pun bisa menambahkannya lewat Impor… (Command-O) atau dengan klik dua kali file tersebut.
Cara Kerja Meter
Meter di bar menu adalah tampilan plus sumber plus pengaturan pengguna. Tampilan adalah folder Anda: halaman web kecil yang dimuat Multimeter ke dalam tampilan transparan setinggi 22 poin, selebar width di manifest Anda. Sumber adalah apa yang diukur meter, dipilih pengguna saat menambah meter.
Setiap kali ada perubahan, Multimeter menyetel window.meterData dan mengirim event meterUpdate. Tampilan Anda tidak perlu tahu dari mana angkanya berasal: gambar level (0 sampai 1) dan tampilkan formatted, dan tampilan itu bekerja sama baiknya untuk CPU, jaringan, timer, atau baterai.
Struktur File
Setiap tampilan adalah folder yang berisi:
my-meter/
├── manifest.json # name, size, which sources it's made for
├── index.html # the page Multimeter loads
├── styles.css
└── meter.js # listens for meterUpdate and drawsmanifest.json
File manifest menjelaskan tampilan Anda dan memberi tahu Multimeter seberapa lebar tampilan itu dan untuk apa dibuat:
{
"name": "My Meter",
"description": "What it shows, in one line.",
"version": "1.0.0",
"author": "Your Name",
"apiVersion": 2,
"sources": ["any"],
"defaultSource": "cpu",
"width": 60,
"minWidth": 40,
"maxWidth": 100,
"supportsLightMode": true,
"supportsDarkMode": true
}| Kunci | Tipe | Artinya |
|---|---|---|
name | string | Ditampilkan di daftar tampilan dan di galeri. |
description | string | Satu baris tentang apa yang ditampilkan tampilan ini. |
version | string | Nomor versi Anda, seperti 1.0.0. |
author | string | Nama Anda, ditampilkan di galeri. |
apiVersion | 2 | Setel ke 2 untuk data yang dijelaskan di halaman ini. |
sources | string[] | ["any"] untuk tampilan yang bisa menggambar sumber apa pun, atau daftar id sumber seperti ["battery"] atau ["battery", "countdown", "timer"]. Multimeter hanya menawarkan tampilan Anda untuk sumber-sumber itu. |
defaultSource | string | Sumber yang dipilih saat seseorang pertama kali menambah tampilan Anda, seperti "cpu". |
width | number | Lebar dalam poin. Tingginya selalu 22. |
minWidth / maxWidth | number | Lebar tersempit dan terlebar yang bisa diatur pengguna di Sesuaikan. |
supportsLightMode / supportsDarkMode | boolean | Apakah tampilan dirancang untuk bar menu terang dan gelap. |
Manifest tanpa apiVersion dan sources dianggap sebagai tampilan yang mengutamakan baterai tetapi tetap bisa dipakai dengan sumber apa pun, jadi meter yang dibuat untuk versi sebelumnya tetap berfungsi.
Data Meter
Multimeter menaruh data terkini di window.meterData dan mengirimnya lagi setiap kali ada perubahan. Berikut semua isinya (API versi 2):
window.meterData = {
apiVersion: 2,
source: "cpu", // what this meter measures
sourceName: "CPU", // that source's name, translated
label: "CPU", // the meter's label ("" if the person cleared it)
value: 23.4, // raw value in `unit` (null if unavailable)
min: 0, max: 100, // the range `level` is measured against
level: 0.234, // value placed in the range, 0 to 1
formatted: "23%", // ready to show, unit included, in the person's language
unit: "%", // "%", "W", "B/s", "rpm", "s", or your own
state: "normal", // "normal", "warning" or "critical"
tint: null, // "#30D158" if the person picked a color
history: [0.21, 0.25], // up to 60 recent levels, oldest first
warningLevel: 0.7, // alert levels on the same 0 to 1 scale (or null)
criticalLevel: 0.9,
alertDirection: "above", // "above" or "below" (or null)
available: true, // false when the source can't be read right now
colorScheme: "dark", // the menu bar: "light" or "dark"
reduceMotion: false, // System Settings > Accessibility > Reduce motion
strings: { timeLeft: "left" },
battery: { // always the Mac's battery, whatever the source
level: 0.82, percentage: 82, isCharging: false, isPluggedIn: false,
hasBattery: true, timeRemaining: 312, timeToFullCharge: null,
formattedTime: "5:12", cycleCount: 212, health: 0.94, temperature: 30.5
},
// Version 1 fields, so battery meters keep working with any source:
percentage: 23, isCharging: false, isPluggedIn: false,
timeRemaining: null, timeToFullCharge: null, formattedTime: null,
cycleCount: 212, health: 0.94, temperature: 30.5, hasBattery: true
}| Kolom | Artinya |
|---|---|
source | Id sumber (lihat tabel di bawah). sourceName adalah namanya yang sudah diterjemahkan. |
label | Label meter, seperti CPU atau DOWN. Pengguna bisa mengganti atau mengosongkannya, jadi nilainya bisa berupa string kosong. |
value | Bacaan mentah dalam unit, atau null. Bersama min dan max, Anda mendapat gambaran lengkapnya. |
level | Nilai yang ditempatkan dalam rentang 0 sampai 1, dibatasi. Kebanyakan tampilan hanya butuh ini. |
formatted | Bacaan sebagai teks, lengkap dengan satuan, diformat sesuai bahasa pengguna: 23%, 12.4 W, 1.2 MB/s, 4:12. |
unit | %, W, B/s, rpm, s (timer dan hitung mundur), string kosong, atau satuan buatan pengguna. |
state | normal, warning, atau critical, dari level peringatan yang diatur pengguna. Warnai tampilan Anda berdasarkan ini. |
tint | Warna seperti #30D158 yang dipilih pengguna, atau null untuk memakai warna tampilan Anda sendiri. |
history | Hingga 60 level terbaru (0 sampai 1), yang terlama lebih dulu, untuk grafik. |
warningLevel | warningLevel dan criticalLevel menempatkan level peringatan pada skala 0 sampai 1 yang sama dengan level, dan alertDirection menunjukkan apakah peringatan berbunyi above atau below level itu. Ketiganya null jika meter tidak punya peringatan. |
available | false saat sumber tidak bisa dibaca sekarang: tidak ada baterai, aksesori mati, alamat web gagal. Tampilkan tanda strip. |
colorScheme | Tampilan bar menu, light atau dark. Mengikuti wallpaper, bukan hanya pengaturan sistem. |
reduceMotion | true saat Kurangi gerakan aktif. Lewati animasi. |
battery | Baterai Mac, selalu ada apa pun yang diukur meter: daya, pengisian, tersambung ke daya, sisa waktu, waktu sampai penuh, siklus, kesehatan, dan suhu. |
Mendengarkan Pembaruan
Gambar sekali dengan apa pun yang sudah ada di window.meterData, lalu gambar lagi pada setiap meterUpdate. Setel window.meterReady = true saat Anda siap menerima data.
function render(data) {
// draw the meter from data.level, data.formatted, data.state ...
}
window.addEventListener('meterUpdate', (e) => render(e.detail));
render(window.meterData || { level: 0.5, formatted: '50%', state: 'normal' });
window.meterReady = true;Sumber
Inilah sumber yang bisa dimiliki meter. Tampilan Anda melihat id-nya di source dan menerima formatted yang sudah jadi, jadi Anda jarang perlu memperlakukannya secara berbeda.
| Sumber | source | unit | Contoh formatted |
|---|---|---|---|
| Baterai | battery | "%" | 82% |
| Aksesori Bluetooth | bluetooth | "%" | 64% |
| Pemakaian Daya | power | "W" | 12.4 W |
| CPU | cpu | "%" | 23% |
| Memori | memory | "%" | 71% |
| Ruang Disk | disk | "%" | 58% |
| Tekanan Termal | thermal | "" | Normal, Cukup, Serius, Kritis |
| Kecepatan Kipas* | fan | "rpm" | 2,400 rpm |
| Jaringan | network | "B/s" | 1.2 MB/s, 340 KB/s |
| Timer | timer | "s" | 24:51 |
| Hitung Mundur | countdown | "s" | 3d 4h, 4:12, 12:05 |
| Kemajuan Hari | progress | "%" | 63% |
| Nilai Kiriman | push | "" | 42 |
| Alamat Web | url | "" | 1,284 |
| File | file | "" | 12 |
| Skrip* | script | "" | 412 |
* Kecepatan kipas dan skrip membutuhkan versi unduhan langsung Multimeter. Versi Mac App Store tidak bisa membaca sensor kipas atau menjalankan skrip Anda; pakai nilai kiriman atau file sebagai gantinya.
Persentase selalu dari 0 sampai 100. Daya, jaringan, dan kecepatan kipas menyesuaikan skala dengan puncak terbarunya kecuali pengguna mengatur rentang. Tekanan termal punya empat tingkat, jadi level bernilai 0, 0.33, 0.67, atau 1. Untuk timer dan hitung mundur, level adalah porsi waktu yang tersisa dan value adalah sisa detiknya.
Peringatan dan Warna
Setiap meter bisa punya level peringatan dan level kritis yang diatur pengguna (bawaan baterai di bawah 20% dan 10%, CPU di atas 70% dan 90%, disk di atas 85% dan 95%). Multimeter menghitung state untuk Anda. Tampilan sebaiknya terlihat tenang saat normal dan makin mencolok saat menjadi warning dan critical, serta memakai tint jika pengguna memilih warna:
document.body.classList.remove('warning', 'critical');
if (data.state !== 'normal') document.body.classList.add(data.state);
// The person's color wins over the face's own
if (data.tint) document.body.style.setProperty('--accent', data.tint);
else document.body.style.removeProperty('--accent');body.warning { --accent: #ff9f0a; }
body.critical { --accent: #ff453a; }Ingin menggambar zona peringatan pada skala, seperti dial dengan ujung merah? Pakai warningLevel, criticalLevel, dan alertDirection.
Riwayat
history menyimpan hingga 60 level terbaru, yang terlama lebih dulu, sudah dalam skala 0 sampai 1. Tambahkan level saat ini di ujungnya dan jadilah grafik. Contoh Grafik Riwayat di bawah menggambar satu.
Detail Baterai
Baterai tetap istimewa. data.battery selalu berisi baterai Mac, apa pun yang diukur meter, jadi tampilan mana pun bisa menampilkan ikon petir pengisian. Tampilan yang dibuat khusus untuk baterai sebaiknya mencantumkan "sources": ["battery"] di manifest-nya. timeRemaining dan timeToFullCharge dalam menit dan bernilai null selama macOS masih memperkirakan.
// A battery-only face: "sources": ["battery"] in manifest.json
const b = data.battery;
bolt.hidden = !b.isCharging;
time.textContent = b.isCharging
? (b.timeToFullCharge ? `${b.timeToFullCharge} min to full` : '')
: (b.formattedTime ?? '');Tampilan yang ditulis untuk versi 1 membaca percentage, isCharging, dan formattedTime di tingkat teratas. Semuanya masih dikirim: untuk sumber baterai, isinya nilai baterai itu sendiri, dan untuk sumber lain isCharging bernilai false dan formattedTime adalah sisa waktu timer atau hitung mundur (h:mm), selain itu null.
Data Anda Sendiri
Empat sumber menampilkan angka yang Anda berikan: nilai kiriman, alamat web, file, atau skrip. Tampilan Anda memperlakukannya seperti sumber lain.
Kirim nilai lewat tautan
Buka tautan multimeter://set dari mana saja: Terminal, skrip shell, Pintasan (Buka URL), Raycast, tugas cron, skrip build. Tambahkan meter dengan sumber Nilai Kiriman dan kunci yang sama, dan meter langsung diperbarui begitu tautan dibuka. open -g membuat Multimeter tetap di latar belakang.
# Set a value (the meter shows the "deploys" key)
open -g "multimeter://set?key=deploys&value=42"
# Range, label, unit and your own text ride along
open -g "multimeter://set?key=coffee&value=3&min=0&max=5&label=Coffee&unit=cups"
open -g "multimeter://set?key=build&value=0.8&max=1&formatted=80%25%20done"| Parameter | Artinya |
|---|---|
key | Untuk meter yang mana. Kunci tidak peka huruf besar-kecil. |
value | Angkanya. Wajib. |
min / max | Rentangnya, agar level keluar dengan benar. Bawaannya 0 sampai 100. |
label | Teks untuk label meter. |
unit | Satuan yang ditambahkan setelah angka, seperti cups. Pakai % untuk persentase. |
formatted | Teks Anda sendiri untuk formatted, dipakai sebagai pengganti angka. |
Nilai kiriman juga berfungsi di versi Mac App Store. Multimeter mengingat nilai terakhir untuk setiap kunci.
Alamat web, file, dan skrip
Ketiganya membaca keluaran yang sama: angka polos, atau JSON dengan value dan tambahan apa pun. Semua selain value bersifat opsional.
42
42.5
42%
{"value": 42, "min": 0, "max": 100, "label": "Deploys", "unit": "", "formatted": "42 left"}Mengarah ke JSON yang lebih besar, seperti respons API? Beri meter jalur kunci dan meter akan mengambil angkanya:
{"data": {"count": 17}} → key path data.count
{"items": [{"value": 9.5}]} → key path items.0.value- Alamat web: diambil sesuai interval yang Anda pilih, paling sering setiap 30 detik (setiap 5 menit kecuali Anda mengubahnya).
- File: dibaca ulang setiap kali file berubah. Praktis untuk apa pun yang bisa menulis file.
- Skrip: perintah shell yang dijalankan dengan
zshsesuai interval yang Anda pilih (setiap 30 detik kecuali Anda mengubahnya). Multimeter menampilkan angka yang dicetaknya. Folder Homebrew ada diPATH, dan perintah yang berjalan lebih dari 10 detik akan dihentikan.
# Open pull requests waiting on you (direct download version)
gh pr list --search "review-requested:@me" --json number --jq length
# Free memory pressure as a percentage
memory_pressure | awk '/percentage/ {print 100 - $5}'Versi Mac App Store bisa memakai alamat web dan file, tetapi tidak skrip. Untuk skrip di sana, buat skrip Anda mengirim tautan atau menulis file.
Bar Menu Terang dan Gelap
Bar menu bisa terang atau gelap tergantung wallpaper. Multimeter menambahkan light-mode atau dark-mode ke body, menyetel colorScheme di data, dan prefers-color-scheme juga mengikuti bar menu. Pakai yang mana pun yang Anda suka:
/* Dark menu bar (the default) */
body { color: white; }
/* Light menu bar */
body.light-mode { color: black; }
/* Same thing with the media query, which follows the menu bar too */
@media (prefers-color-scheme: light) {
body { color: black; }
}Gerakan Dikurangi
Saat Kurangi gerakan aktif, reduceMotion bernilai true dan prefers-reduced-motion cocok. Jaga meter tetap mudah dibaca dan buang pantulan, denyut, dan sapuannya:
body.reduce-motion * { transition: none !important; animation: none !important; }
@media (prefers-reduced-motion: reduce) {
* { transition: none !important; animation: none !important; }
}Contoh Lengkap
Dua tampilan lengkap, berjalan langsung di halaman ini lewat data yang sama dengan yang dikirim Multimeter. Salin file ke sebuah folder lalu impor.
Tampilan awal dari Buat Tampilan
Persis yang ditulis Buat Tampilan…: label, bar, dan nilai terformat, dengan penjelasan API di komentarnya. Tampilan ini mewarnai dirinya berdasarkan state, menghormati tint, dan mengikuti tampilan bar menu.
starter/manifest.json
{
"name": "My Face",
"description": "A label, a bar and the value. Works with any source.",
"version": "1.0.0",
"author": "Your Name",
"apiVersion": 2,
"sources": ["any"],
"defaultSource": "cpu",
"width": 70,
"minWidth": 50,
"maxWidth": 120,
"supportsLightMode": true,
"supportsDarkMode": true
}starter/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div id="meter">
<span id="label"></span>
<span id="track"><span id="fill"></span></span>
<span id="value">--</span>
</div>
<script src="meter.js"></script>
</body>
</html>starter/styles.css
* { margin: 0; padding: 0; box-sizing: border-box; }
/* The meter is 22 points tall and as wide as manifest.json says. */
body {
font: 600 11px/1 -apple-system, BlinkMacSystemFont, sans-serif;
font-variant-numeric: tabular-nums;
color: white; /* dark menu bar */
background: transparent;
height: 22px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
--accent: #30d158; /* replaced by the meter's color, if one is set */
}
/* Light menu bar: the system setting, or the preview's light mode. */
#track { background: rgba(255, 255, 255, 0.25); }
@media (prefers-color-scheme: light) { body { color: black; } #track { background: rgba(0, 0, 0, 0.18); } }
body.light-mode { color: black; }
body.light-mode #track { background: rgba(0, 0, 0, 0.18); }
body.dark-mode { color: white; }
body.dark-mode #track { background: rgba(255, 255, 255, 0.25); }
#meter { display: flex; align-items: center; gap: 4px; }
#label { font-size: 8px; letter-spacing: 0.04em; opacity: 0.7; }
#track { width: 18px; height: 6px; border-radius: 3px; position: relative; overflow: hidden; }
#fill { position: absolute; inset: 0 auto 0 0; width: 0; background: var(--accent); border-radius: 3px; transition: width 0.4s ease; }
/* state is "normal", "warning" or "critical" (from the meter's alert levels). */
body.warning { --accent: #ffd60a; }
body.critical { --accent: #ff453a; }
@media (prefers-reduced-motion: reduce) { #fill { transition: none; } }starter/meter.js
// Multimeter meter API, version 2. The app sets window.meterData and fires
// a 'meterUpdate' event whenever anything changes. The fields you'll use most:
//
// level 0.0 to 1.0, the value within the meter's range
// formatted ready-to-show text with its unit: "23%", "1.2 MB/s", "4:12"
// label short label ("CPU"), may be "" if hidden
// value, unit, min, max the raw number and its range
// state "normal", "warning" or "critical"
// tint the color picked in Customize ("#30D158"), or null
// history up to 60 recent levels (0 to 1), oldest first, for graphs
// source what it measures: "battery", "cpu", "memory", "network", "timer"...
// available false when there's nothing to read (no battery, URL failing)
// battery the Mac's battery, always: level, isCharging, isPluggedIn,
// hasBattery, timeRemaining, timeToFullCharge, health...
// isCharging true only when this meter shows the battery and it's charging
// colorScheme "light" or "dark"; reduceMotion true if animations should stop
//
// Full reference: https://multimeterapp.com/creating-meters
class Meter {
constructor() {
this.label = document.getElementById('label');
this.fill = document.getElementById('fill');
this.value = document.getElementById('value');
window.addEventListener('meterUpdate', (e) => this.render(e.detail));
if (window.meterData) this.render(window.meterData);
window.meterReady = true;
}
render(data) {
const level = Math.max(0, Math.min(1, data.level || 0));
this.label.textContent = data.label || '';
this.label.style.display = data.label ? '' : 'none';
this.value.textContent = data.available === false ? '\u2013' : (data.formatted || Math.round(level * 100) + '%');
this.fill.style.width = (level * 100) + '%';
document.body.classList.toggle('warning', data.state === 'warning');
document.body.classList.toggle('critical', data.state === 'critical');
if (data.tint) document.body.style.setProperty('--accent', data.tint);
else document.body.style.removeProperty('--accent');
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => new Meter());
} else {
new Meter();
}Grafik Riwayat
Grafik garis satu menit terakhir dari history, digambar di canvas dengan resolusi layar yang sebenarnya.
history-graph/manifest.json
{
"name": "History Graph",
"description": "The last minute as a little line graph.",
"version": "1.0.0",
"author": "Your Name",
"apiVersion": 2,
"sources": ["any"],
"defaultSource": "network",
"width": 44,
"minWidth": 30,
"maxWidth": 80,
"supportsLightMode": true,
"supportsDarkMode": true
}history-graph/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
html, body { margin: 0; height: 22px; background: transparent; overflow: hidden; }
canvas { display: block; width: 100%; height: 22px; }
</style>
</head>
<body>
<canvas id="graph"></canvas>
<script src="meter.js"></script>
</body>
</html>history-graph/meter.js
const canvas = document.getElementById('graph');
const ctx = canvas.getContext('2d');
function render(data) {
// Draw at the screen's real resolution so the line stays sharp
const scale = window.devicePixelRatio || 1;
const w = canvas.clientWidth, h = 22;
canvas.width = w * scale;
canvas.height = h * scale;
ctx.setTransform(scale, 0, 0, scale, 0, 0);
ctx.clearRect(0, 0, w, h);
const points = (data.history || []).concat(data.level ?? 0);
if (points.length < 2) return;
const dark = data.colorScheme !== 'light';
const color = data.tint
|| (data.state === 'critical' ? '#ff453a' : data.state === 'warning' ? '#ff9f0a' : dark ? '#fff' : '#1d1d1f');
const x = (i) => (i / (points.length - 1)) * (w - 2) + 1;
const y = (v) => h - 3 - v * (h - 6);
// Soft fill under the line, then the line
ctx.beginPath();
points.forEach((v, i) => (i ? ctx.lineTo(x(i), y(v)) : ctx.moveTo(x(i), y(v))));
ctx.lineTo(x(points.length - 1), h);
ctx.lineTo(x(0), h);
ctx.closePath();
ctx.globalAlpha = 0.22;
ctx.fillStyle = color;
ctx.fill();
ctx.globalAlpha = 1;
ctx.beginPath();
points.forEach((v, i) => (i ? ctx.lineTo(x(i), y(v)) : ctx.moveTo(x(i), y(v))));
ctx.strokeStyle = color;
ctx.lineWidth = 1.5;
ctx.lineJoin = 'round';
ctx.stroke();
}
window.addEventListener('meterUpdate', (e) => render(e.detail));
render(window.meterData || { history: [0.2, 0.4, 0.3, 0.6, 0.5], level: 0.7 });
window.meterReady = true;Tips Pengujian
- Jaga semuanya dalam tinggi 22 poin, dan periksa tampilan Anda pada
minWidthdanmaxWidth. - Pakai font sistem (
-apple-system) danfont-variant-numeric: tabular-numsagar angka tidak bergoyang. - Coba tampilan Anda dengan beberapa sumber di Tambah Meter… dan Sesuaikan Meter Ini…: persentase, kecepatan jaringan, timer, sumber yang tidak tersedia.
- Periksa bar menu terang dan gelap, warna pilihan, serta status peringatan dan kritis.
- Error dari JavaScript Anda muncul di Console.app. Anda juga bisa membuka
index.htmldi Safari dan mengirim data dari konsol: - Jaga ukuran file tetap kecil dan hindari permintaan jaringan, agar tampilan langsung tergambar.
// Paste in Safari's console with your index.html open to try a value
window.meterData = { level: 0.92, formatted: '92%', label: 'CPU', state: 'critical',
available: true, colorScheme: 'dark', history: [] };
window.dispatchEvent(new CustomEvent('meterUpdate', { detail: window.meterData }));Siap membuat?
$19.99 sekali bayar. Tanpa langganan. Uang kembali dalam 30 hari.
Beli seharga $19.99