Was brauchen wir für das Werkzeug:
→ Ordnerstruktur & Dateien (siehe How-to: Basic Setup)
→ Browser (funktioniert am besten mit Chrome)
→ Localhost (siehe How-to: Localhost)
→ Text-Editor (zb. SublimeText)
Die index.html beinhaltet u.A.:
→ <script> zum Einbinden der benötigten Scripte
→ <h1> Überschrift (optional)
→ <p> Anleitungstext (optional)
→ <button> zum Hinzuf ügen von Textfeldern
→ <input> zum Einstellen der Schriftgröße
→ <div> als Container f ür <video> und <canvas>
1. Öffne die index.html
Du kannst den Code kopieren und in deine Datei einfügen:
<!DOCTYPE html>
<html>
<head>
<title>Textfelder</title>
<link rel="stylesheet" type="text/css" href="css/style.css">
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils@0.1/camera_utils.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils@0.1/drawing_utils.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands@0.1/hands.js" crossorigin="anonymous"></script>
</head>
<body>
<h1>Textfeld Tool</h1>
<p>Drücke auf "Neues Textfeld hinzufügen" und ziehe mit Zeigefinger und Daumen die Textbox auf. <br>
Halte die Textbox konstant in Größe und Position und sie wird in der Form auf der Leinwand fixiert. <br>
Funktioniert am besten wenn du gut ausgeleuchtet bist.
</p>
<button id="startButton">Neues Textfeld hinzufügen</button>
<input class="slider" type="range" min="1" max="100" value="50" oninput="handleSliderChange(this.value)">
<div class="container">
<video class="input_video" autoplay playsinline></video>
<canvas class="output_canvas" width="1480px" height="820px"></canvas>
</div>
<script src="js/script.js"></script>
</body>
</html>
2. Speicher die index.html
1. Öffne die style.css
Die style.css beinhaltet:
→ *{} Zücksetzen auf den Default Style
→ h1{}, p{}, button{}, .slider{} basic Styling (optinal)
→ .container{} legt Größe fest und spiegelt Video und Gesten (wichtig)
→ .input_video{} definieren der Größe des Videos (wichtig)
→ .text-area{} Einstellungen für die Textboxen (wichtig)
Du kannst den Code kopieren und in deine style.css Datei einfügen:
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
width: 100vw;
height: 100vw;
}
h1{
margin: 20px;
}
p {
margin-left: 20px;
}
button {
margin: 20px;
}
h1, p, button {
z-index: 9999999 !important;
}
.slider {
position: relative;
margin-top: 10px;
}
.container {
position: absolute;
transform: scaleX(-1);
width: 100vw;
height: 100vh;
top: 0;
z-index: -9999999 !important;
}
.input_video {
position: relative;
top: 0;
left: 0;
width: 100vw;
background-color: white;
height: 100vh;
object-fit: cover;
}
.text-area {
position: absolute;
font-size: 26px;
padding: 5px;
text-align: left;
white-space: pre-wrap;
overflow-wrap: break-word;
resize: none;
z-index: 9999999999!important;
text-decoration: none !important;
}
2. Speicher die style.css
Die script.js beinhaltet:
→ createTextArea: Wird definiert, um ein neues Textbereichs-Element zu erstellen und dessen Eigenschaften festzulegen
→ updateTextAreaPosition: Wird definiert, um die Position des Textfelds basierend auf den Positionen des Zeige- und Daumenfingers zu aktualisieren.
→ onResults: Wird definiert und als Rückruffunktion für die Handgesten-Ergebnisse aufgerufen
→ updateTextAreaSize: Aktualisiert die Größe des Textfelds basierend auf dem Abstand zwischen den Fingern
→ resetTextArea: Setzt das Textfeld zurück
→ handleSliderChange: Verarbeitet Schieberegleränderungen für die Schriftgröße des Textfeld
→ startButton.addEventListener: Button startet und stoppt das Tracking der Handgesten
1. Öffne die script.js
Du kannst den Code kopieren und in deine script.js Datei einfügen:
const videoElement = document.getElementsByClassName('input_video')[0];
const canvasElement = document.getElementsByClassName('output_canvas')[0];
const canvasCtx = canvasElement.getContext('2d');
let indexFingerPosition = null;
let thumbPosition = null;
let textAreaSize = 0;
let textArea = createTextArea();
document.body.appendChild(textArea);
let isPositionFixed = false;
let isSizeStable = false;
let stableSizeCount = 0;
const stableSizeThreshold = 25;
const sizeTolerance = 10;
let startTime = Date.now();
let stableSize = textAreaSize;
function createTextArea() {
const newTextArea = document.createElement('textarea');
newTextArea.classList.add('text-area');
newTextArea.style.width = `${textAreaSize}px`;
newTextArea.style.height = `${textAreaSize}px`;
newTextArea.style.display = 'none';
return newTextArea;
}
function updateTextAreaPosition() {
if (indexFingerPosition && thumbPosition) {
const avgX = (indexFingerPosition.x + thumbPosition.x) / 2;
const avgY = (indexFingerPosition.y + thumbPosition.y) / 2;
if (!isPositionFixed) {
const textAreaWidth = textArea.offsetWidth;
const textAreaHeight = textArea.offsetHeight;
textArea.style.left = `${avgX - (textAreaWidth / 2)}px`;
textArea.style.top = `${avgY - (textAreaHeight / 2)}px`;
}
}
}
function onResults(results) {
canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
if (results.multiHandLandmarks && results.multiHandedness) {
for (let index = 0; index < results.multiHandLandmarks.length; index++) {
const classification = results.multiHandedness[index];
const isRightHand = classification.label === 'Right';
const landmarks = results.multiHandLandmarks[index];
const indexFingerLandmark = landmarks[8];
const thumbLandmark = landmarks[4];
if (indexFingerLandmark && thumbLandmark) {
const indexFingerX = canvasElement.width - (indexFingerLandmark.x * canvasElement.width);
const indexFingerY = indexFingerLandmark.y * canvasElement.height;
const thumbX = canvasElement.width - (thumbLandmark.x * canvasElement.width);
const thumbY = thumbLandmark.y * canvasElement.height;
indexFingerPosition = { x: indexFingerX, y: indexFingerY };
thumbPosition = { x: thumbX, y: thumbY };
const dx = indexFingerX - thumbX;
const dy = indexFingerY - thumbY;
const distance = Math.sqrt(dx * dx + dy * dy);
updateTextAreaSize(distance);
}
}
}
updateTextAreaPosition();
}
const hands = new Hands({
locateFile: (file) => {
return `https://cdn.jsdelivr.net/npm/@mediapipe/hands@0.1/${file}`;
}
});
hands.onResults(onResults);
const camera = new Camera(videoElement, {
onFrame: async () => {
await hands.send({ image: videoElement });
}
});
function updateTextAreaSize(distance) {
const minDistance = 50;
const maxDistance = 300;
const minSize = 50;
const maxSize = 200;
const size = (distance - minDistance) / (maxDistance - minDistance) * (maxSize - minSize) + minSize;
if (!isSizeStable) {
if (Math.abs(size - stableSize) <= sizeTolerance) {
stableSizeCount++;
if (stableSizeCount >= stableSizeThreshold) {
isSizeStable = true;
isPositionFixed = true;
textArea.style.position = 'fixed';
}
} else {
stableSize = size;
stableSizeCount = 0;
}
}
textAreaSize = stableSize;
textArea.style.width = `${textAreaSize}px`;
textArea.style.height = `${textAreaSize}px`;
}
function resetTextArea() {
indexFingerPosition = null;
thumbPosition = null;
textAreaSize = 0;
isPositionFixed = false;
isSizeStable = false;
stableSizeCount = 0;
textArea = createTextArea();
document.body.appendChild(textArea);
}
function handleSliderChange(value) {
const newSize = value;
textArea.style.fontSize = `${newSize}px`;
}
const startButton = document.getElementById('startButton');
let isTrackingStarted = true;
startButton.addEventListener('click', () => {
if (!isTrackingStarted) {
isTrackingStarted = true;
startButton.disabled = true;
textArea.style.display = 'block';
slider.style.display = 'block';
} else {
resetTextArea();
textArea.style.display = 'block';
slider.style.display = 'block';
startButton.textContent = 'Neues Textfeld platzieren';
}
});
startTime = Date.now();
window.addEventListener('load', () => {
camera.start();
});
2. Speicher die script.js