Skomplikowane interfejsy dotykowe a React

...czyli ludzie mają jedną myszkę ale 10 palców.

(I dlaczego nas to obchodzi.)

O mnie

Head of Engineering w Vazco

Od 2016 z React'em na produkcji

To i wszystko inne jest na radekmie.dev

Szczęśliwy posiadacz 10 palców

Tyle możliwości!

  1. Pinch

  2. Pan

  3. Tap

  4. Double tap

  5. Press

  6. Drag and drop

  7. (API)

I za dużo rozwiązań!

onAuxClick, onAuxClickCapture, onClick, onClickCapture, onDoubleClick, onDoubleClickCapture, onDrag, onDragCapture, onDragEnd, onDragEndCapture, onDragEnter, onDragEnterCapture, onDragOver, onDragOverCapture, onDragStart, onDragStartCapture, onDrop, onDropCapture, onMouseDown, onMouseDownCapture, onMouseEnter, onMouseLeave, onMouseMove, onMouseMoveCapture, onMouseOut, onMouseOutCapture, onMouseUp, onMouseUpCapture, onPointerCancel, onPointerCancelCapture, onPointerDown, onPointerDownCapture, onPointerEnter, onPointerLeave, onPointerMove, onPointerMoveCapture, onPointerOut, onPointerOutCapture, onPointerUp, onPointerUpCapture, onScroll, onScrollCapture, onTouchCancel, onTouchCancelCapture, onTouchEnd, onTouchEndCapture, onTouchMove, onTouchMoveCapture, onTouchStart, onTouchStartCapture, onWheel, onWheelCapture

<Canvas> czy DOM

  • Łatwa integracja
  • Obsługa zdarzeń
  • Pełne wsparcie CSS
  • Realizowane na GPU
  • Skaluje się bez rozmycia

SVG!

<Canvas> czy DOM

export function Room({ room, tables }) {
  return (










  );
}

Nic niezwykłego

 
 
    <svg viewBox={`0 0 ${room.width} ${room.height}`}>
      <g>






      </g>
    </svg>
 
 
 
 
 
 
        {room.visuals.map((visual, index) => (
          <RoomVisual key={index} visual={visual} />
        ))}

        
        
        
 
 
 
 
 






        {tables.map(table => (
          <Table table={table} key={table._id} />
        ))}
 
 
 
 

<Canvas> czy DOM

export function RoomVisual({ visual }) {

  
  
  
  
  
  
  
  
}

Poważnie, nic niezwykłego

 
  if (visual.type === 'circ') {
    return <circle cx={...} cy={...} r={...} />;
  }

    
    
    
    
    
    
 




  if (visual.type === 'rect') {
    return <rect height={...} width={...} />;
  }


 
 








  // ...
 

Gdzie te zdarzenia?

Akcje = funkcje

import { useState } from 'react';

export function RoomInteractive(props) {

  
  
  
}

Interakcje = zdarzenia

 


  const state = useState(initialRoomState);



 
 



  const actionsRef = useActions(state, props);


 
 




  const svgRef = useInteractions(actionsRef, props);

 
 





  return <Room ref={svgRef} {...} />;
 

Akcje

function useActions(state, props) {

  
  
  
  
  
  
  
  
  
  
  
}
 
  const actions = {
    instances: useRef({}),
    onClockClick(reservationId) {
      // `props` i `state` są tutaj aktualne.
    },
    onClocksPress(selectedTableModule) {},
    onSeatClick(tableId, seatIndex) {}
  };




 
 










  actionsRef.current = actions;

 
 









  const actionsRef = useRef(actions);


 
 











  return actionsRef;
 

Stała referencja do funkcji i instancji

Interakcje

 

function useInteractions(actionsRef, props) {
  const svgRef = createRef();

  
  
  
  
  
  
  
  
  
  
  return svgRef;
}
 



  useEffect(() => {

    
    
    
    
    
    
    
    
  }, [props.room._id]);
 
 
import svgPanZoom from 'svg-pan-zoom';




    const { instances } = actionsRef.current;
    instances.svgElement = svgRef.current;
    instances.svgPanZoom = svgPanZoom(svgRef.current, {
      customEventsHandler: {
        init: () => 
        destroy: () => 
      },
    });
    return () => instances.svgPanZoom.destroy();


 
 








                    instances.hammer = initHammer(...),
                       instances.hammer.destroy(),

      
      
      
      
      

Interakcje

 

function initHammer() {

  
  
  
  
  
  
  
  
  
  
  
  
}
import Hammer from 'hammerjs';

 
  const hammer = new Hammer(svgElement, {
    recognizers: [
      [Hammer.Pan, { direction: Hammer.DIRECTION_ALL }],
      [Hammer.Pinch],
      [Hammer.Press],
      [Hammer.Tap, { event: 'doubletap', taps: 2 }],
      [Hammer.Tap],
    ],
  });




 
 











  hammer.get('doubletap').recognizeWith('tap');
  hammer.get('tap').requireFailure('doubletap');


 
 













  hammer.on('press', event => /* ... */);
  // ...
 

Podsumowanie

  1. Zwykłe drzewo komponentów React'owych renderuje SVG
  2. svg-pan-zoom obsługuje poruszanie się i zoom
  3. Hammer.js rozróżnia zdarzenia i woła nasze funkcje
  4. Jasna separacja renderowania i obsługi zdarzeń

Chwila, co z props?

W miejscu obsługi zdarzeń nie wiemy, który komponent (np. stolik) dokładnie został kliknięty

<g data-table={table._id} data-type="table">
  ...
</g>
const table = event.target.closest('[data-type=table]');
if (!table) { return; }

const tableId = table.dataset.table;

Czy to wciąż React!?

Nie

obchodzi

mnie

🥰

to

Pytania?

Skomplikowane interfejsy dotykowe a React

By Radosław Miernik

Skomplikowane interfejsy dotykowe a React

ReactJS Wroclaw #27

  • 229