Язык жестов

Анна Селезнёва, Spiral Scout

Источник картинки: NewScientist

Язык жестов

Новосибирск, 23-25 ноября

Обо мне

Анна Селезнёва

Lead frontend developer @ Spiral Scout

#freehugs

История

Источник картинки: VRDB

Touch-устройства

План

Спецификации

Touch Events

www.w3.org/TR/touch-events

Pointer Events

www.w3.org/TR/pointerevents

Touch Events vs Pointer Events

Patrick Lauke

Patrick Lauke

Презентация: patrickhlauke.github.io/getting-touchy-presentation

Touch EventsPointer Events

Mouse Events

Источник картинки: Medium
      if (window.PointerEvent) {
  target.addEventListener('pointerdown', (event) => {
    // handle event
  });
} else {
  target.addEventListener('touchstart', (event) => {
    event.preventDefault();
    // handle event
  });
  target.addEventListener('mousedown',(event) => {
    // handle event
  });
}
      
    

Pointer Events Polyfill

PEP Logo

github.com/jquery/PEP

ReactJS Pointer Events (16.4+)

onPointerDown, onPointerMove, onPointerUp,
onGotPointerCapture, onLostPointerCapture,
onPointerEnter, onPointerLeave,
onPointerOver, onPointerOut,
onPointerCancel

События

pointerdown

touchstart

mousedown

}
down

pointermove

touchmove

mousemove

}
move

pointerup

touchend

mouseup

}
up

Стандартные жесты

Tap

События:down

Press

Press

События:down + up

click (~ 300ms)

Pan

События:down + move + up

Drag'n'Drop

Drag'n'Drop

      <div draggable="true"> </div>
    

События: dragstart, drag, dragend
dragenter, dragover, dragleave

Drag'n'Drop

Drag'n'Drop

События:down + move + up

Swipe / Flick

События:down + move + up

      const dx = endX - startX;
const dy = endY - startY;

if (Math.abs(dx + dy) > distanceThreshold) {
  if (Math.abs(dx) > Math.abs(dy)) {
    if (dx < 0) {
      // Swipe / Flick Left
    }
  }
}
      
    

Double tap

События:down + up

      const currentTime = new Date().getTime();
const tapDuration = currentTime - lastTime;
clearTimeout(timeout);
if (tapDuration > 0 && tapDuration < timeThreshold) {
  event.preventDefault();
  // Double tap
} else {
  timeout = setTimeout(() => { /* interrupted */ }, timeThreshold);
}
lastTime = currentTime;
      
    

Hold

События:down + up

Hold

      requestAnimationFrame(detectHold);
function detectHold() {
  if (duration < holdDuration) {
    requestAnimationFrame(detectHold);
    duration++;
  } else {
    // Hold
  }
}
      
    

Жесты несколькими пальцами

Определение для Touch

      
const fingers = event.touches;
      
    

Определение для Pointer

      const fingers = [];

function handlePointerDown(event) {
  fingers.push(event);
}
      
    
      function handlePointerMove(event) {
  fingers.forEach(({ pointerId }, i) => {
    if (event.pointerId === pointerId) fingers[i] = event;
  });
}
function handlePointerUpOrCancel(event) {
  fingers.forEach(({ pointerId }, i) => {
    if (event.pointerId === pointerId) fingers.splice(i, 1);
  });
}
      
    

Pinch & Spread / Zoom

События:down + move + up

      if (fingers.length === 2) {
  const curDiff = Math.abs(
    fingers[0].clientX - fingers[1].clientX
  );
  if (prevDiff > 0) {
    if (curDiff > prevDiff) { /* Spread / Zoom In */ }
    if (curDiff < prevDiff) { /* Pinch / Zoom Out */ }
  }
  prevDiff = curDiff;
}
      
    

Rotate

События:down + move + up

        if (fingers.length === 2) {
    const dx = Math.abs(
      fingers[0].clientX - fingers[1].clientX
    );
    const dy = Math.abs(
      fingers[0].clientY - fingers[1].clientY
    );
    const angle = Math.atan2(dy, dx);
    // Rotate
  }
        
      

Область нажатия для Pointer

      target.addEventListener('pointerdown', (event) => {
  const area = event.width * event.height;
  // Use area
});
      
    

Область нажатия для Touch (Level 2)

      target.addEventListener('touchstart', (event) => {
  const touch = e.touches[0];
  const area = touch.radiusX * touch.radiusY;
  // Use area
});
      
    

Готовые решения

hammerjs.github.io

Жесты: pan, pinch, press, rotate, swipe, tap

      const hammertime = new Hammer(element);
hammertime.on('rotate', (event) => {
	// Rotate
});
      
    

Жесты с использованием устройства

Screen Orientation

      window.addEventListener('orientationchange', () => {
  // Handle orientation change
});
      
    

Screen Orientation

      const orientation = screen.msOrientation || (screen.orientation || {}).type;
if (orientation) {
  // 'landscape-primary' || 'landscape-secondary'
  // 'portrait-primary' || 'portrait-secondary'
} else {
  const angle = Math.abs(window.orientation);
  if (angle === 90) {
  // Landscape
  } else {
  // Portrait
  }
}
      
    

Device Orientation

developers.google.com/web/fundamentals/native-hardware/device-orientation/

Device Orientation & Device Motion

      if (window.DeviceOrientationEvent) {
  // Listen deviceorientation
}
if (window.DeviceMotionEvent) {
  // Listen devicemotion
}
      
    

Device Orientation & Device Motion

Tilt

      if (window.DeviceOrientationEvent) {
  window.addEventListener('deviceorientation', (event) => {
    const { alpha, beta, gamma } = event;
    // Handle tilt
  });
}
      
  
      const cube = document.getElementById('cube');
cube.style.transform = `
  rotateX(${beta}deg)
  rotateY(${gamma}deg)
  rotateZ(${alpha}deg)
`;
      
    

askd.rocks/pres/gestures/cube.html

Shake

      if (window.DeviceMotionEvent) {
  window.addEventListener('devicemotion', (event) => {
    const { x, y, z } = event.accelerationIncludingGravity;
    // Handle shake
  });
}
      
    
      const dx = Math.abs(lastX - x);
const dy = Math.abs(lastY - y);
const dz = Math.abs(lastZ - z);
if ((dx > threshold && dy > threshold) || (dx > threshold && dz > threshold)
 || (dy > threshold && dz > threshold)) {
  currentTime = new Date().getTime();
  if (currentTime - lastTime > timeout) {
    // Shake
    lastTime = currentTime;
  }
}
lastX = x; lastY = y; lastZ = z;
      
    

3D touch

      const handleTouchStartOrMove = (event) => {
  const force = event.touches[0].force;
  // Use force value (0..1)
};
    
        // iOS 10+
element.addEventListener('touchforcechange', (event) => {
  const force = event.changedTouches[0].force;
  // Use force value (0..1)
});
        
      

Сила нажатия для Pointer

        const handlePointerEvent = (event) => {
    const pressure = event.pressure;
    // Use pressure value
  };
        
      

Floating Touch

Источник: Sony Floating Touch technology explained

Жесты с использованием человека

Источник картинки: Progressive Infotech

FacePause Chrome Extension

github.com/Hemmingsson/FacePause

FaceDetector API

      const faceDetector = new FaceDetector();
    

wicg.github.io/shape-detection-api/#face-detection-api

FaceDetector API

      faceDetector.detect(imageOrCanvas)
  .then((faces) => {
    // Use faces array
  }
  .catch((e) => {
    // Handle error
  });
      
    

FaceDetector + WebCam

codepen.io/askd/pen/OBqJGP

Итоги

Жесты –
это новые клики

Главный редактор UX Planet Ник Бабич

Правила

  1. Используйте стандартные жесты
  2. Не используйте стандартные жесты для нестандартных действий
  3. Не вмешивайтесь в системные жесты устройства
  4. Используйте жесты как дополнение, а не замену стандартным действиям
  5. Используйте расширенные жесты для улучшения UX

Спасибо!

asktwi

anna.selezniova

askd.rocks


Презентация: askd.rocks/pres/gestures