Controles de movimiento en el navegador

 

 

 

  • Clase magistral de tipografía, con Elliot Jay Stocks
  • Implemente rápidamente. Implementar inteligentemente

  • Índice
    1. Paso 1: obtenga los datos del video
    2. Paso 2: siga los movimientos de la mano
    3. Paso 3: detectar gestos
    4. Conclusión

    Si alguna vez quisiste crear una aplicación web que puedas controlar con gestos con las manos como por arte de magia, este artículo es para ti. Con un par de API y algo de JavaScript, puedes crear aplicaciones que se comporten como si fueran brujería.

     

    En este artículo, voy a explicar cómo implementar controles de movimiento en el navegador. Eso significa que podrás crear una aplicación donde podrás mover la mano y hacer gestos, y los elementos en la pantalla responderán.

    He aquí un ejemplo:

    Consulte el lápiz [Magic Hand - Controles de movimiento para la web [bifurcado]] (https://codepen.io/smashingmag/pen/vYrEEYw) de Yaphi .

    De todos modos, hay algunos ingredientes principales que necesitarás para que los controles de movimiento funcionen para ti:

    1. Datos de vídeo de una cámara web;
    2. Aprendizaje automático para rastrear los movimientos de las manos;
    3. Lógica de detección de gestos.

    Nota : este artículo asume una familiaridad general con HTML, CSS y JavaScript, por lo que si la tienes, podemos comenzar. También tenga en cuenta que es posible que deba hacer clic en las demostraciones de CodePen en caso de que alguna vista previa aparezca en blanco (permisos de cámara no otorgados).

    Paso 1: obtenga los datos del video

    El primer paso para crear controles de movimiento es acceder a la cámara del usuario. Podemos hacerlo usando la getMediaDevicesAPI del navegador .

    A continuación se muestra un ejemplo que obtiene los datos de la cámara del usuario y los dibuja canvascada 100 milisegundos:

    Consulte el Pen [Prueba API de cámara (MediaDevices) [bifurcado]](https://codepen.io/smashingmag/pen/QWxwwbG) de Yaphi .

    Del ejemplo anterior, este código le proporciona los datos del vídeo y los dibuja en el lienzo:

    const constraints = { audio: false, video: { width, height }};navigator.mediaDevices.getUserMedia(constraints) .then(function(mediaStream) { video.srcObject = mediaStream; video.onloadedmetadata = function(e) { video.play(); setInterval(drawVideoFrame, 100); }; }) .catch(function(err) { console.log(err); });function drawVideoFrame() { context.drawImage(video, 0, 0, width, height); // or do other stuff with the video data}

    Cuando ejecuta getUserMedia, el navegador comienza a grabar datos de la cámara después de pedir permiso al usuario. El constraintsparámetro te permite indicar si quieres incluir vídeo y audio y, si tienes vídeo, cuál quieres que sea su resolución.

     

    Los datos de la cámara vienen como un objeto conocido como MediaStream, que luego puedes incluir en un videoelemento HTML a través de su srcObjectpropiedad. Una vez que el vídeo esté listo, lo inicias y luego haces lo que quieras con los datos del cuadro. En este caso, el ejemplo de código dibuja un fotograma de vídeo en el lienzo cada 100 milisegundos.

    Puedes crear más efectos de lienzo con tus datos de video, pero para los propósitos de este artículo, sabes lo suficiente como para pasar al siguiente paso.

    Paso 2: siga los movimientos de la mano

    Ahora que puede acceder a datos cuadro por cuadro de una transmisión de video desde una cámara web, el siguiente paso en su búsqueda para crear controles de movimiento es descubrir dónde están las manos del usuario. Para este paso, necesitaremos aprendizaje automático.

    Para que esto funcione, utilicé una biblioteca de aprendizaje automático de código abierto de Google llamada MediaPipe . Esta biblioteca toma datos de cuadros de video y le brinda las coordenadas de múltiples puntos (también conocidos como landmarks) en sus manos.

    Ésa es la belleza de las bibliotecas de aprendizaje automático: la tecnología compleja no tiene por qué ser compleja de usar.

    Aquí está la biblioteca en acción:

    Consulte el Pen [Prueba MediaPipe [bifurcado]](https://codepen.io/smashingmag/pen/XWYJJpY) de Yaphi .

    Coordenadas manuales representadas sobre un lienzo. ( Vista previa grande )

    A continuación se incluye un texto estándar para comenzar (adaptado del ejemplo de la API de JavaScript de MediaPipe ):

    script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js" crossorigin="anonymous"/scriptscript src="https://cdn.jsdelivr.net/npm/@mediapipe/control_utils/control_utils.js" crossorigin="anonymous"/scriptscript src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js" crossorigin="anonymous"/scriptscript src="https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js" crossorigin="anonymous"/scriptvideo/videocanvas/canvasscriptconst videoElement = document.querySelector('.input_video');const canvasElement = document.querySelector('.output_canvas');const canvasCtx = canvasElement.getContext('2d');function onResults(handData) { drawHandPositions(canvasElement, canvasCtx, handData);}function drawHandPositions(canvasElement, canvasCtx, handData) { canvasCtx.save(); canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height); canvasCtx.drawImage( handData.image, 0, 0, canvasElement.width, canvasElement.height); if (handData.multiHandLandmarks) { for (const landmarks of handData.multiHandLandmarks) { drawConnectors(canvasCtx, landmarks, HAND_CONNECTIONS, {color: '#00FF00', lineWidth: 5}); drawLandmarks(canvasCtx, landmarks, {color: '#FF0000', lineWidth: 2}); } } canvasCtx.restore();}const hands = new Hands({locateFile: (file) = { return `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`;}});hands.setOptions({ maxNumHands: 1, modelComplexity: 1, minDetectionConfidence: 0.5, minTrackingConfidence: 0.5});hands.onResults(onResults);const camera = new Camera(videoElement, { onFrame: async () = { await hands.send({image: videoElement}); }, width: 1280, height: 720});camera.start();/script

    El código anterior hace lo siguiente:

     

    • Cargue el código de la biblioteca;
    • Comience a grabar los cuadros de video;
    • Cuando lleguen los datos de las manos, dibuje los puntos de referencia de las manos en un lienzo.

    Echemos un vistazo más de cerca al handDataobjeto, ya que ahí es donde ocurre la magia. En el interior handDatase encuentra multiHandLandmarksuna colección de 21 coordenadas para las partes de cada mano detectadas en el video. Así es como se estructuran esas coordenadas:

    { multiHandLandmarks: [ // First detected hand. [ {x: 0.4, y: 0.8, z: 4.5}, {x: 0.5, y: 0.3, z: -0.03}, // ...etc. ], // Second detected hand. [ {x: 0.4, y: 0.8, z: 4.5}, {x: 0.5, y: 0.3, z: -0.03}, // ...etc. ], // More hands if other people participate. ]}

    Un par de notas:

    • La primera mano no significa necesariamente la mano derecha o izquierda; es simplemente el que la aplicación detecte primero. Si desea obtener una mano específica, deberá verificar qué mano se está detectando usando handData.multiHandedness[0].labely potencialmente intercambiando los valores si su cámara no está reflejada.
    • Por motivos de rendimiento, puedes restringir el número máximo de manos a seguir, lo que hicimos antes configurando maxNumHands: 1.
    • Las coordenadas se establecen en una escala de 0a 1según el tamaño del lienzo.

    Aquí hay una representación visual de las coordenadas de la mano:

    Un mapa de puntos numerados en una mano. (Fuente: github.io ) ( Vista previa grande )

    Ahora que tiene las coordenadas del punto de referencia de la mano, puede crear un cursor para seguir su dedo índice. Para hacer eso, necesitarás obtener las coordenadas del dedo índice.

    Podrías usar la matriz directamente de esta manera handData.multiHandLandmarks[0][5], pero me resulta difícil realizar un seguimiento, así que prefiero etiquetar las coordenadas de esta manera:

    const handParts = { wrist: 0, thumb: { base: 1, middle: 2, topKnuckle: 3, tip: 4 }, indexFinger: { base: 5, middle: 6, topKnuckle: 7, tip: 8 }, middleFinger: { base: 9, middle: 10, topKnuckle: 11, tip: 12 }, ringFinger: { base: 13, middle: 14, topKnuckle: 15, tip: 16 }, pinky: { base: 17, middle: 18, topKnuckle: 19, tip: 20 },};

    Y luego puedes obtener las coordenadas así:

     

    const firstDetectedHand = handData.multiHandLandmarks[0];const indexFingerCoords = firstDetectedHand[handParts.index.middle];

    Encontré que el movimiento del cursor es más agradable de usar con la parte media del dedo índice que con la punta porque la parte media es más estable.

    Ahora necesitarás crear un elemento DOM para usarlo como cursor. Aquí está el marcado:

    div/div

    Y aquí están los estilos: Significado de los nombres

    .cursor { height: 0px; width: 0px; position: absolute; left: 0px; top: 0px; z-index: 10; transition: transform 0.1s;}.cursor::after { content: ''; display: block; height: 50px; width: 50px; border-radius: 50%; position: absolute; left: 0; top: 0; transform: translate(-50%, -50%); background-color: #0098db;}

    Algunas notas sobre estos estilos:

    • El cursor está absolutamente posicionado para que pueda moverse sin afectar el flujo del documento.
    • La parte visual del cursor está en el ::afterpseudoelemento y se transformasegura de que la parte visual del cursor esté centrada alrededor de las coordenadas del cursor.
    • El cursor tiene una transitionpara suavizar sus movimientos.

    Ahora que hemos creado un elemento de cursor, podemos moverlo convirtiendo las coordenadas de la mano en coordenadas de página y aplicando esas coordenadas de página al elemento de cursor.

    function getCursorCoords(handData) { const { x, y, z } = handData.multiHandLandmarks[0][handParts.indexFinger.middle]; const mirroredXCoord = -x + 1; /* due to camera mirroring */ return { x: mirroredXCoord, y, z };}function convertCoordsToDomPosition({ x, y }) { return { x: `${x * 100}vw`, y: `${y * 100}vh`, };}function updateCursor(handData) { const cursorCoords = getCursorCoords(handData); if (!cursorCoords) { return; } const { x, y } = convertCoordsToDomPosition(cursorCoords); cursor.style.transform = `translate(${x}, ${y})`;}function onResults(handData) { if (!handData) { return; } updateCursor(handData);}

    Tenga en cuenta que estamos usando la transformpropiedad CSS para mover el elemento en lugar de lefty top. Esto es por razones de rendimiento. Cuando el navegador muestra una vista, sigue una secuencia de pasos . Cuando el DOM cambia, el navegador debe comenzar de nuevo en el paso de renderizado relevante. La transformpropiedad responde rápidamente a los cambios porque se aplica en el último paso en lugar de en uno de los pasos intermedios y, por lo tanto, el navegador tiene menos trabajo que repetir.

    Ahora que tenemos un cursor funcional, estamos listos para continuar.

    Paso 3: detectar gestos

    El siguiente paso en nuestro viaje es detectar gestos, específicamente gestos de pellizco .

    Primero, ¿qué entendemos por pellizco? En este caso, definiremos un pellizco como un gesto en el que el pulgar y el índice están lo suficientemente juntos.

    Para designar un pellizco en el código, podemos observar cuando las coordenadas x, yy zdel pulgar y el índice tienen una diferencia lo suficientemente pequeña entre ellos. “Lo suficientemente pequeño” puede variar según el caso de uso, así que siéntete libre de experimentar con diferentes rangos. Personalmente, encontré 0.08que , 0.08y 0.11son cómodos para las coordenadas , y, xrespectivamente y. zAsí es como se ve:

     

    function isPinched(handData) { const fingerTip = handData.multiHandLandmarks[0][handParts.indexFinger.tip]; const thumbTip = handData.multiHandLandmarks[0][handParts.thumb.tip]; const distance = { x: Math.abs(fingerTip.x - thumbTip.x), y: Math.abs(fingerTip.y - thumbTip.y), z: Math.abs(fingerTip.z - thumbTip.z), }; const areFingersCloseEnough = distance.x 0.08 distance.y 0.08 distance.z 0.11; return areFingersCloseEnough;}

    Sería bueno si eso fuera todo lo que tuviéramos que hacer, pero, por desgracia, nunca es tan sencillo.

    ¿Qué sucede cuando tus dedos están al borde de una posición de pellizco? Si no tenemos cuidado, la respuesta es el caos.

    Con ligeros movimientos de los dedos, así como fluctuaciones en la detección de coordenadas, nuestro programa puede alternar rápidamente entre estados pellizcados y no pellizcados. Si intenta utilizar un gesto de pellizco para “recoger” un elemento en la pantalla, puede imaginar lo caótico que sería que el elemento alternara rápidamente entre ser recogido y dejado caer.

    Para evitar que nuestros gestos de pellizcar causen caos, necesitaremos introducir un ligero retraso antes de registrar un cambio de un estado pellizcado a un estado sin pellizcar o viceversa. Esta técnica se llama debouncey la lógica es la siguiente:

    • Cuando los dedos entren en estado de pellizco, inicie un cronómetro.
    • Si los dedos han permanecido comprimidos de forma ininterrumpida durante un tiempo suficiente, registre un cambio.
    • Si el estado comprimido se interrumpe demasiado pronto, detenga el cronómetro y no registre ningún cambio.

    El truco es que el retraso debe ser lo suficientemente largo para que sea confiable pero lo suficientemente corto para que parezca rápido.

    Pronto llegaremos al código antirrebote, pero primero debemos prepararnos rastreando el estado de nuestros gestos:

    const OPTIONS = { PINCH_DELAY_MS: 60,};const state = { isPinched: false, pinchChangeTimeout: null,};

    A continuación, prepararemos algunos eventos personalizados para que sea más conveniente responder a los gestos:

    const PINCH_EVENTS = { START: 'pinch_start', MOVE: 'pinch_move', STOP: 'pinch_stop',};function triggerEvent({ eventName, eventData }) { const event = new CustomEvent(eventName, { detail: eventData }); document.dispatchEvent(event);}

    Ahora podemos escribir una función para actualizar el estado pinchado:

    function updatePinchState(handData) { const wasPinchedBefore = state.isPinched; const isPinchedNow = isPinched(handData); const hasPassedPinchThreshold = isPinchedNow !== wasPinchedBefore; const hasWaitStarted = !!state.pinchChangeTimeout; if (hasPassedPinchThreshold !hasWaitStarted) { registerChangeAfterWait(handData, isPinchedNow); } if (!hasPassedPinchThreshold) { cancelWaitForChange(); if (isPinchedNow) { triggerEvent({ eventName: PINCH_EVENTS.MOVE, eventData: getCursorCoords(handData), }); } }}function registerChangeAfterWait(handData, isPinchedNow) { state.pinchChangeTimeout = setTimeout(() = { state.isPinched = isPinchedNow; triggerEvent({ eventName: isPinchedNow ? PINCH_EVENTS.START : PINCH_EVENTS.STOP, eventData: getCursorCoords(handData), }); }, OPTIONS.PINCH_DELAY_MS);}function cancelWaitForChange() { clearTimeout(state.pinchChangeTimeout); state.pinchChangeTimeout = null;}

    Esto es lo que updatePinchState()está haciendo:

     

    • Si los dedos han superado el umbral de pellizco al iniciar o detener un pellizco, iniciaremos un temporizador para esperar y ver si podemos registrar un cambio de estado de pellizco legítimo.
    • Si se interrumpe la espera, eso significa que el cambio fue solo una fluctuación, por lo que podemos cancelar el cronómetro.
    • Sin embargo, si el temporizador no se interrumpe, podemos actualizar el estado bloqueado y activar el evento de cambio personalizado correcto, es decir, pinch_starto pinch_stop.
    • Si los dedos no han superado el umbral de cambio de pellizco y actualmente están pellizcados, podemos enviar un pinch_moveevento personalizado.

    Podemos ejecutar updatePinchState(handData)cada vez que obtengamos datos manuales para poder ponerlos en nuestra onResultsfunción de esta manera:

    function onResults(handData) { if (!handData) { return; } updateCursor(handData); updatePinchState(handData);}

    Ahora que podemos detectar de manera confiable un cambio de estado de pellizco, podemos usar nuestros eventos personalizados para definir cualquier comportamiento que queramos cuando se inicia, mueve o detiene un pellizco. He aquí un ejemplo:

    document.addEventListener(PINCH_EVENTS.START, onPinchStart);document.addEventListener(PINCH_EVENTS.MOVE, onPinchMove);document.addEventListener(PINCH_EVENTS.STOP, onPinchStop);function onPinchStart(eventInfo) { const cursorCoords = eventInfo.detail; console.log('Pinch started', cursorCoords);}function onPinchMove(eventInfo) { const cursorCoords = eventInfo.detail; console.log('Pinch moved', cursorCoords);}function onPinchStop(eventInfo) { const cursorCoords = eventInfo.detail; console.log('Pinch stopped', cursorCoords);}

    Ahora que hemos cubierto cómo responder a movimientos y gestos, tenemos todo lo que necesitamos para crear una aplicación que pueda controlarse con movimientos de la mano.

    Aquí hay unos ejemplos:

    Mira el bolígrafo [Beam Sword - ¡Diversión con controles de movimiento! [bifurcado]](https://codepen.io/smashingmag/pen/WNybveM) por Yaphi .

    Vea el bolígrafo [Magic Quill - Escritura aérea con controles de movimiento [bifurcado]] (https://codepen.io/smashingmag/pen/OJEPVJj) de Yaphi .

    También he reunido algunas otras demostraciones de control de movimiento, incluidos naipes móviles y un plano de planta de un apartamento con imágenes móviles de los muebles, y estoy seguro de que se te ocurren otras formas de experimentar con esta tecnología.

    Conclusión

    Si has llegado hasta aquí, habrás visto cómo implementar controles de movimiento con un navegador y una cámara web. Ha leído datos de la cámara utilizando las API del navegador, ha obtenido coordenadas de las manos mediante el aprendizaje automático y ha detectado movimientos de las manos con JavaScript. Con estos ingredientes, puedes crear todo tipo de aplicaciones controladas por movimiento.

    ¿Qué casos de uso se te ocurrirán? ¡Házmelo saber en los comentarios!

    (yk, il)Explora más en

    • Navegadores
    • API
    • Aplicaciones
    • javascript





    Tal vez te puede interesar:

    1. ¿Deberían abrirse los enlaces en ventanas nuevas?
    2. 24 excelentes tutoriales de AJAX
    3. 70 técnicas nuevas y útiles de AJAX y JavaScript
    4. Más de 45 excelentes recursos y repositorios de fragmentos de código

    Controles de movimiento en el navegador

    Controles de movimiento en el navegador

    Clase magistral de tipografía, con Elliot Jay Stocks Implemente rápidamente. Implementar inteligentemente Índice

    programar

    es

    https://aprendeprogramando.es/static/images/programar-controles-de-movimiento-en-el-navegador-1159-0.jpg

    2024-04-04

     

    Controles de movimiento en el navegador
    Controles de movimiento en el navegador

    Si crees que alguno de los contenidos (texto, imagenes o multimedia) en esta página infringe tus derechos relativos a propiedad intelectual, marcas registradas o cualquier otro de tus derechos, por favor ponte en contacto con nosotros en el mail [email protected] y retiraremos este contenido inmediatamente

     

     

    Top 20