自身の端末から出退勤打刻するページとは別に、QRコードカードを読み取り、打刻できるページを新たに作成します。
Contents
QRコードカード + iPhoneカメラ読み取り
構成例
- カード:社員ごとのQRコードカード
- 端末:iPhone(SafariまたはPWAアプリ)
- 読み取り:iPhoneカメラ + JavaScript(WebRTC)でQRを読み取り
- 記録:Firestoreなどに打刻時間・ユーザー情報を保存
メリット
- iPhoneだけで完結可能
- カード印刷も容易
- Webアプリ上で完結しやすい
デメリット
- 不正使用の可能性(QRコードコピー防止の工夫必要)
qr-attendance.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>QRコードで出退勤打刻</title>
<script src="https://www.gstatic.com/firebasejs/10.11.0/firebase-app-compat.js"></script>
<script src="https://www.gstatic.com/firebasejs/10.11.0/firebase-firestore-compat.js"></script>
<script src="https://unpkg.com/@zxing/library@latest"></script>
<style>
body {
font-family: sans-serif;
text-align: center;
padding: 20px;
}
video {
border: 1px solid #ccc;
width: 300px;
height: 200px;
}
button {
margin: 10px;
padding: 10px 20px;
font-size: 16px;
}
#result {
margin-top: 20px;
font-size: 16px;
color: green;
}
#clock {
font-size: 18px;
margin-bottom: 10px;
color: #555;
}
</style>
</head>
<body>
<h1>QRコードで出退勤打刻</h1>
<div id="clock">--:--:--</div>
<video id="preview" autoplay muted playsinline></video>
<div>
<button onclick="setType('出勤')">出勤モード</button>
<button onclick="setType('退勤')">退勤モード</button>
</div>
<p id="result">モードを選択してください</p>
<script>
// Firebase 設定
const firebaseConfig = {
apiKey: "あなたのAPIキー",
authDomain: "あなたのプロジェクトID.firebaseapp.com",
projectId: "あなたのプロジェクトID",
storageBucket: "あなたのプロジェクトID.appspot.com",
messagingSenderId: "送信者ID",
appId: "アプリID"
};
firebase.initializeApp(firebaseConfig);
const db = firebase.firestore();
let currentType = null;
let codeReader = null;
let cameraTimeout = null; // タイマー用変数
// 時計表示
function updateClock() {
const now = new Date();
const timeStr = now.toLocaleString('ja-JP', { hour12: false });
document.getElementById("clock").textContent = timeStr;
}
setInterval(updateClock, 1000);
updateClock();
function setType(type) {
// 前回のカメラを停止
if (codeReader) {
codeReader.reset();
}
if (cameraTimeout) {
clearTimeout(cameraTimeout);
}
currentType = type;
document.getElementById("result").innerText = `${type}モードです。QRコードをかざしてください。`;
startCamera();
}
function startCamera() {
const previewElement = document.getElementById('preview');
codeReader = new ZXing.BrowserQRCodeReader();
// 1分後に自動停止
cameraTimeout = setTimeout(() => {
codeReader.reset();
document.getElementById("result").innerText = "カメラは1分後に自動停止しました。再度モードを選択してください。";
}, 60000);
codeReader.decodeOnceFromVideoDevice(undefined, previewElement)
.then(async (result) => {
clearTimeout(cameraTimeout);
const uid = result.text;
const nowDate = new Date();
let latitude = null;
let longitude = null;
let address = "住所不明";
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(async (pos) => {
latitude = pos.coords.latitude;
longitude = pos.coords.longitude;
try {
const res = await fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${latitude}&lon=${longitude}&accept-language=ja`);
const data = await res.json();
address = data.display_name || address;
} catch (e) {
console.warn("住所取得に失敗:", e);
}
saveAttendance(uid, nowDate, latitude, longitude, address);
}, (err) => {
console.warn("位置情報取得失敗:", err);
saveAttendance(uid, nowDate, latitude, longitude, address);
});
} else {
saveAttendance(uid, nowDate, latitude, longitude, address);
}
document.getElementById("result").innerText = `${uid} の ${currentType} を記録中...`;
codeReader.reset();
})
.catch((err) => {
clearTimeout(cameraTimeout);
console.error("QRコードの読み取りに失敗:", err);
document.getElementById("result").innerText = "QRコードの読み取りに失敗しました。もう一度試してください。";
});
}
function saveAttendance(uid, now, latitude, longitude, address) {
db.collection("attendance").add({
uid: uid,
type: currentType,
timestamp: firebase.firestore.Timestamp.fromDate(now),
location: {
latitude,
longitude,
address
}
})
.then(() => {
document.getElementById("result").innerText = `${uid} の ${currentType} を記録しました(${now.toLocaleString()})`;
})
.catch((error) => {
document.getElementById("result").innerText = `記録に失敗: ${error.message}`;
});
}
</script>
</body>
</html>
コード解説
このQRコードで出退勤打刻を行うWebアプリのコードは、以下のような構成と仕組みになっています。
✅ 全体の目的
「出勤 or 退勤ボタンを押すとカメラが起動し、QRコードを読み取って、位置情報付きで出退勤をFirebaseに記録する」という流れです。
🔧 構成と解説
🔹 1. HTML構造
<video id="preview" autoplay muted playsinline></video>
- カメラ映像を表示するビデオ要素。
<button onclick="setType('出勤')">出勤モード</button>
<button onclick="setType('退勤')">退勤モード</button>
- 出勤 or 退勤モードを選択するボタン。押すとカメラが起動します。
<div id="clock">--:--:--</div>
- 常に更新される現在時刻。
<p id="result"></p>
- 処理結果やエラーメッセージを表示します。
🔹 2. Firebase 初期化
const firebaseConfig = {
// Firebaseプロジェクトの設定
};
firebase.initializeApp(firebaseConfig);
const db = firebase.firestore();
- Firestore データベースにアクセスするための初期設定。
🔹 3. 現在時刻を常時表示
function updateClock() {
const now = new Date();
const timeStr = now.toLocaleString('ja-JP', { hour12: false });
document.getElementById("clock").textContent = timeStr;
}
setInterval(updateClock, 1000);
- 毎秒現在時刻を更新して画面に表示しています。
🔹 4. モード切替とカメラ起動
function setType(type) {
currentType = type;
document.getElementById("result").innerText = `${type}モードです。QRコードをかざしてください。`;
startCamera();
}
出勤
or退勤
のどちらかのモードを選び、カメラを起動します。
🔹 5. カメラ起動とQRコード読み取り
function startCamera() {
const previewElement = document.getElementById('preview');
codeReader = new ZXing.BrowserQRCodeReader();
codeReader.decodeOnceFromVideoDevice(undefined, previewElement)
decodeOnceFromVideoDevice()
は、1回だけQRコードを読み取ってカメラを自動停止してくれる便利な関数です。
.then(async (result) => {
const uid = result.text;
const nowDate = new Date();
- QRコードから取得した文字列(社員のuidなど)を変数
uid
に格納。 nowDate
は現在の日時。
🔹 6. 位置情報の取得
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(async (pos) => {
latitude = pos.coords.latitude;
longitude = pos.coords.longitude;
- ブラウザから現在の位置情報(緯度・経度)を取得します。
const res = await fetch(`https://nominatim.openstreetmap.org/reverse?...`);
- オープン地図APIを使って住所を取得します。
🔹 7. 出退勤の保存処理
function saveAttendance(uid, now, latitude, longitude, address) {
db.collection("attendance").add({
uid: uid,
type: currentType,
timestamp: firebase.firestore.Timestamp.fromDate(now),
location: {
latitude,
longitude,
address
}
})
attendance
コレクションに次の情報を保存:uid
: 誰がtype
: 出勤 or 退勤timestamp
: いつlocation
: 緯度・経度・住所
🔹 8. QR読み取り後のカメラ停止
codeReader.reset();
- QRコード読み取りが成功したら、カメラを停止します。
- 電力節約や誤動作防止のため。
✅ 主な技術とライブラリ
技術 | 説明 |
---|---|
Firebase Firestore | 出退勤データの保存 |
ZXing (@zxing/library ) | QRコードの読み取り |
Geolocation API | 現在の緯度・経度を取得 |
OpenStreetMap (Nominatim API) | 緯度経度 → 住所への変換 |
JavaScript | 全体の制御、DOM操作、非同期処理 |
✅ 例:Firestoreに保存されるデータ
{
"uid": "abc123",
"type": "出勤",
"timestamp": "2025-05-19T22:10:00+09:00",
"location": {
"latitude": 35.6895,
"longitude": 139.6917,
"address": "東京都新宿区..."
}
}