добавление креста метки на видео

This commit is contained in:
Tot Maxim
2026-05-11 16:18:26 +03:00
parent ae56334637
commit 361ffc5578
3 changed files with 167 additions and 6 deletions

View File

@@ -341,6 +341,70 @@ h1 {
object-fit: contain;
background: black;
}
/* ===== CAMERA OVERLAY ===== */
.cam-frame-wrapper {
position: relative;
flex: 1;
width: 100%;
background: black;
overflow: hidden;
}
.cam-frame {
width: 100%;
height: 100%;
object-fit: contain;
background: black;
}
/* Перекрестие (crosshair) */
.cam-crosshair {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 10;
}
/* Вертикальная линия */
.crosshair-v {
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
width: 1px;
height: 100%;
background: #00ff88;
box-shadow: 0 0 4px rgba(0, 255, 136, 0.3);
}
/* Горизонтальная линия */
.crosshair-h {
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
width: 100%;
height: 1px;
background: #00ff88;
box-shadow: 0 0 4px rgba(0, 255, 136, 0.3);
}
/* Центральный квадрат */
.crosshair-box {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 120px;
height: 120px;
border: 2px solid #00ff88;
box-shadow:
0 0 10px rgba(0, 255, 136, 0.3),
inset 0 0 10px rgba(0, 255, 136, 0.1);
}
/* ===== MOBILE ===== */
@media (max-width: 500px) {

View File

@@ -86,7 +86,19 @@
<div class="cam-header">
<span>Видео с камеры</span>
</div>
<img id="camImage" src="" alt="Camera" class="cam-frame">
<div class="cam-frame-wrapper">
<img id="camImage" src="" alt="Camera" class="cam-frame">
<!-- ПЕРЕКРЕСТИЕ -->
<div class="cam-crosshair" id="crosshair">
<!-- Основные линии -->
<div class="crosshair-v"></div>
<div class="crosshair-h"></div>
<!-- Центральный квадрат -->
<div class="crosshair-box"></div>
</div>
</div>
</div>
</div>

View File

@@ -3,14 +3,17 @@ import { updateResizerVisibility } from './layout.js';
let rightPanel;
let camImage;
let camClose;
let crosshair;
let isOpen = false;
let streamCheckInterval = null;
export function initCamera() {
rightPanel = document.getElementById("rightPanel");
camImage = document.getElementById("camImage");
camClose = document.getElementById("camClose");
crosshair = document.getElementById("crosshair");
// Проверяем доступность потока
checkStreamAvailability();
const btn = document.getElementById("camToggle");
@@ -23,6 +26,11 @@ export function initCamera() {
rightPanel.classList.remove("hidden");
// Показываем перекрестие
if (crosshair) {
crosshair.style.display = "block";
}
camImage.src = streamUrl;
if (btn) {
@@ -32,6 +40,9 @@ export function initCamera() {
console.log("[camera] opened");
updateResizerVisibility();
// Запускаем мониторинг потока
startStreamMonitoring();
}
function close() {
@@ -39,6 +50,11 @@ export function initCamera() {
isOpen = false;
// Скрываем перекрестие
if (crosshair) {
crosshair.style.display = "none";
}
// остановка stream
camImage.src = "";
@@ -51,6 +67,9 @@ export function initCamera() {
console.log("[camera] closed");
updateResizerVisibility();
// Останавливаем мониторинг
stopStreamMonitoring();
}
function toggle() {
@@ -63,14 +82,80 @@ export function initCamera() {
camImage.onload = () => {
console.log("[camera] stream started");
if (crosshair) {
crosshair.style.opacity = '1';
}
};
camImage.onerror = (e) => {
console.error("[camera] stream error", e);
if (crosshair) {
crosshair.style.opacity = '0.3';
}
};
btn?.addEventListener("click", toggle);
// автозапуск
open();
// Проверка доступности камеры
async function checkStreamAvailability() {
try {
const response = await fetch('/api/stream');
const data = await response.json();
if (data.available && data.cam) {
console.log("[camera] stream available:", data.source);
// Автозапуск если камера доступна
if (!isOpen) {
open();
}
} else {
console.log("[camera] stream not available");
if (crosshair) {
crosshair.style.display = 'none';
}
}
} catch (error) {
console.log("[camera] check failed:", error);
}
}
// Мониторинг состояния потока
function startStreamMonitoring() {
stopStreamMonitoring();
streamCheckInterval = setInterval(() => {
if (isOpen && camImage) {
if (!camImage.complete || camImage.naturalWidth === 0) {
if (crosshair) {
crosshair.style.opacity = '0.3';
}
} else {
if (crosshair) {
crosshair.style.opacity = '1';
}
}
}
}, 2000);
}
function stopStreamMonitoring() {
if (streamCheckInterval) {
clearInterval(streamCheckInterval);
streamCheckInterval = null;
}
}
}
// Экспортируем для возможности ручного управления
export function showCrosshair(show = true) {
const crosshair = document.getElementById("crosshair");
if (crosshair) {
crosshair.style.display = show ? "block" : "none";
}
}
export function setCrosshairOpacity(opacity) {
const crosshair = document.getElementById("crosshair");
if (crosshair) {
crosshair.style.opacity = opacity;
}
}