JavaScript/Tutorials/Mouse and More

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

Im vorliegenden Artikel wird gezeigt, wie man auf einem Element einen rechteckigen Bereich, eine Region of Interest (RoI) markieren kann, und wie man dann die Koordinaten des Rechtecks für die Ausschnittsvergrößerung einer SVG verwenden kann.

Zum Reagieren auf Maus- oder Touch-Aktionen werden Maus-, Touch- und Pointerevents berücksichtigt. Neben Browsern, die nur Maus- und Touchevents berücksichtigen, gib es auch Browser die Maus- und Pointerevents berücksichtigen, und es gibt Browser, die alle drei Modelle unterstützen. Das hier vorgestellte Script berücksichtigt alle drei Fälle.

Das HTML[Bearbeiten]

Das benötigte HTML besteht zuerst nur aus einem leeren figure-Element, dessen Größe im CSS angegeben wird.

Beispiel: HTML ( noch ohne Funktion)
<!DOCTYPE html>
<html lang="de">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>RoI</title>
    <style>
      #roi_target { padding:0; border:1px dotted black; width:200px; height:200px }
    </style>
  </head>

  <body>
    <h1>Region of Interest</h1>
    <main>
      <p>Im Feld unter diesem Text kann man mit der Maus oder mit zwei Fingern ein Rechteck aufziehen.</p>		
      <figure id="roi_target"></figure>
    </main>
  </body>
</html>

Das Javascript[Bearbeiten]

Alle benötigten Methoden und Variablen werden in der Funktion get_roi angelegt. Diese Funktion benötigt als Übergabeparameter die ID des Elements, in dem das Rechteck gezeichnet werden soll, sowie eine Referenz auf die Callback-Funktion, die nach dem Markieren des Rechtecks aufgerufen wird. Die Callback-Funktion wird mit den Koordinaten der Eckpunkte des Rechtecks als Parameter aufgerufen.

Zeichnen des Rechtecks[Bearbeiten]

Zum Zeichnen des Rechtecks wird im Figure-Element ein canvas-Element angelegt. Weiter werden Funktionen zum Zeichnen und Löschen des Rechtecks angelegt. Um das canvas-Element über den Inhalt des figure-Elements legen zu können, erhält das figure-Element die Positionsangabe relativ.

Markieren mit der Maus[Bearbeiten]

Zum Markieren des rechteckigen Bereichs mit der Maus werden die Eventhandler für mousedown, mousemove und mouseup benötigt. Die Mauskoordinaten werden über die Eigenschaften clientX und clientY des Eventobjekts ausgelesen. Da diese Werte relativ zum Viewport genommen werden, wurde mit getBoundingClientRect noch die Position des Figure-Elements relativ zum Viewport ermittelt.

Bei mousedown werden die Mauskoordinaten als Eckkoordinaten für das Rechteck gespeichert. Bei mousemove wird die Position der Maus für die gegenüberliegende Ecke verwendet und das Rechteck wird gezeichnet. Bei mouseup ist die Auswahl abgeschlossen und die Callbackfunktion wird aufgerufen.

Um die Defaultaktionen der Maus zu unterdrücken, wird in den Eventhandlern die Methode preventDefault aufgerufen.

Damit der Eventhandler für mousemove nur bei gedrückter Maustaste aktiv ist, wird die Statusvariable mouse_active entsprechend gesetzt und ausgewertet. Die Statusvariable moved verhindert, dass die Callbackfunktion mit einem Rechteck der Größe 0 aufgerufen wird.

Beispiel: Ermitteln der Mauskoordinaten beim mousemove
  offset = roi.getBoundingClientRect();

    ...

  xe = event.clientX - offset.left;
  ye = event.clientY - offset.top;

Markieren mit den Touch-Events[Bearbeiten]

Hier werden Eventhandler für touchstart, touchmove und touchend benötigt. Das Auslesen der Touchcoordinaten geht über das Array targetTouches, das für jeden Touch die Koordinaten liefert. Die ersten beiden Elemente von targetTouches werden verwendet, um das Rechteck zu zeichnen. Beim Event touchend wird die Callbackfunktion aufgerufen. Beim Touch mit einem Finder erfolgt die Behandlung analog zum Maus-Event, bei zwei Fingern werden die Touches als Eckpunkte genommen.

Da sich die Verarbeitung vom Mouse und Touch-Aktion nur in der Behandlung des Move-Events unterscheiden, werden für touchstart und touchend die gleichen Handlerfunktionen verwendet, wie für mousedown und mouseup. In den Handlern wird dann geprüft, ob Touch- oder Maus-Aktionen vorliegen.

Da es Geräte gibt, die sowohl Touch- als auch Maus-Interaktionen unterstützen, werden die Eventhandler für Touch- und für Maus-Events angelegt.

Beispiel: Ermitteln der Touchkoordinaten beim touchmove
if(event.targetTouches.length>1) {
  xs = event.targetTouches[0].clientX - offset.left;
  ys = event.targetTouches[0].clientY - offset.top;
  xe = event.targetTouches[1].clientX - offset.left;
  ye = event.targetTouches[1].clientY - offset.top;
}
else {
  xe = event.targetTouches[0].clientX - offset.left;
  ye = event.targetTouches[0].clientY - offset.top;
}

Markieren mit den Pointer-Events[Bearbeiten]

Bei den Pointer Events wurde eine andere Philosophie verfolgt. Hier gibt es ein Eventmodell für Maus-, Touch- und Stift-Aktionen. Über entsprechende Eigenschaften des Eventobjekts wird den Eventhandlern mitgeteilt, wie interagiert wurde. Bei Multitouch erhält jeder Touch eine eigene ID. Allerdings gibt es keine Struktur analog zu targetTouches. Hier müssen in den Eventhandlern die Daten der verschiedenen Touches gespeichert und aktualisiert werden. Im Eventhandler für pointerdown werden die Eventobjekte im Array pointereventcache zwischengespeichert und im Handler für pointermove wird über die ID der entsprechende Eintrag gesucht und aktualisiert. Bei Pointerevents wird mit preventDefault die Defaultaktion des Browsers nicht unterdrückt. preventDefault verhindert nur das Weiterreichen des Events z.B. an das Click-Event. Hier muss die CSS-Eigenschaft touch-action = "none" gesetzt werden. Auch in diesem Fall werden die Pointerkoordinaten wieder über clientX/Y ausgelesen. Für pointerdown und -up werden ebenfalls wieder die Handler von mousedown und -up verwendet. Lediglich für pointermove wird ein eigener Eventhandler definiert.

Auch beim Pointerdown wird geprüft, ob mit einem oder zwei Fingern getouched wurde. Die Behandlung erfolgt dann analog zu den Touch-Events.

Beim Anlegen der Eventhandler wird erst geprüft, ob Pointer Events unterstützt werden. In diesem Fall werden Maus- und Touch-Interaktionen über Pointer Events abgewickelt. Werden keine Pointer Events unterstützt, werden für Touches die Touch-Events verwendet und für Mausaktionen die Maus-Events.

Beispiel: Ermitteln der Koordinaten beim pointermove
for (var i=0; i<pointereventcache.length; i++) {
  if (event.pointerId == pointereventcache[i].pointerId) {
    pointereventcache[i] = event;
    break;
  }
}
if(pointereventcache.length>1) {
  xs = pointereventcache[0].clientX - offset.left;
  ys = pointereventcache[0].clientY - offset.top;
  xe = pointereventcache[1].clientX - offset.left;
  ye = pointereventcache[1].clientY - offset.top;
}
else {
  xe = pointereventcache[0].clientX - offset.left;
  ye = pointereventcache[0].clientY - offset.top;
}

Das vollständige Script[Bearbeiten]

Im folgendem Beispiel werden Mouse- Touch- und Pointerevents berücksichtigt:

Beispiel ansehen …
<!DOCTYPE html>
<html lang="de">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>RoI</title>
    <style type="text/css">
      #roi_target { padding:0; border:1px dotted black; width:200px; height:200px }
    </style>

    <script>
"use strict";

window.addEventListener("load",function() {

  get_roi("roi_target", function(xs,ys,xe,ye) { 
    console.log(xs,ys,xe,ye);
  });

},false);


// Bei einem Element mit Maus oder Touch einen rechteckigen Bereich (Region of Interest) auswählen
"use strict";
var get_roi = function(ele,callback) { 
// ele: ID des sensiblen Elements
// callback: Funktion, die nach dem Markieren aufgerufen wird
  var roi = document.getElementById(ele);
  if(!roi) {
    console.error(ele + " nicht gefunden!");
    return;
  }
// Target-Element relativ positioniern und Canvas für das RoI-Rechteck anlegen
  roi.style.position = "relative";
  roi.style.cursor = "crosshair";
  var roi_width = roi.clientWidth;
  var roi_height = roi.clientHeight;
  var  cv = document.createElement("canvas");
  cv.style.position = "absolute";
  cv.width = roi_width;
  cv.height = roi_height;
  cv.style.left = 0;
  cv.style.top = 0;
  roi.appendChild(cv);
  var ctx = cv.getContext("2d");
  ctx.lineWidth = 1;
  ctx.strokeStyle = 'gray';

// Ermitteln der unterstützten Methoden  
  var can_touch = ("TouchEvent" in window);
  var can_pointer = ("PointerEvent" in window);
  
// Statusvariablen
  var pointer_active = false;
  var touch_active = false;
  var mouse_active = false;
  var moved = false;
  
// Weitere Variablen
  var xs=0,xe=roi_width,ys=0,ye=roi_height,offset,pointereventcache=[];
  
// Zeichnen eines Rechtecks
  var draw_rect = function(xs,ys,xe,ye) {
    ctx.clearRect(0,0,roi_width,roi_height);
    ctx.strokeRect(xs,ys,xe-xs,ye-ys);
  } // draw_rect
  
// Löschen des Rechtecks
  var clear_rect = function() {
    ctx.clearRect(0,0,roi_width,roi_height);
  } // clear_rect

// Eventhandler für Mausbewegung
  var move_mouse = function(event) {
    if(mouse_active) {
      moved = true;
      event.preventDefault();
      xe = event.clientX - offset.left;
      ye = event.clientY - offset.top;
      draw_rect(xs,ys,xe,ye);
    }
  } // move_mouse

// Eventhandler für einen oder mehrere bewegte Pointer (touch)
  var move_touch = function(event) {
    if(touch_active) {
      moved = true;
      event.preventDefault();
      if(event.targetTouches.length>1) {
        xs = event.targetTouches[0].clientX - offset.left;
        ys = event.targetTouches[0].clientY - offset.top;
        xe = event.targetTouches[1].clientX - offset.left;
        ye = event.targetTouches[1].clientY - offset.top;
      }
      else {
        xe = event.targetTouches[0].clientX - offset.left;
        ye = event.targetTouches[0].clientY - offset.top;
      }
      draw_rect(xs,ys,xe,ye);
    }
  } // move_touch
  
// Eventhandler für einen oder mehrere bewegte Pointer (pointer)
  var move_pointer = function(event) {
    if(pointer_active) {
      moved = true;
      event.preventDefault();
      for (var i=0; i<pointereventcache.length; i++) {
        if (event.pointerId == pointereventcache[i].pointerId) {
          pointereventcache[i] = event;
          break;
        }
      }
      if(pointereventcache.length>1) {
        xs = pointereventcache[0].clientX - offset.left;
        ys = pointereventcache[0].clientY - offset.top;
        xe = pointereventcache[1].clientX - offset.left;
        ye = pointereventcache[1].clientY - offset.top;
      }
      else {
        xe = pointereventcache[0].clientX - offset.left;
        ye = pointereventcache[0].clientY - offset.top;
      }
      draw_rect(xs,ys,xe,ye);
    }
  } // move_pointer

// Eventhandler für das Ende der Aktion bei mouseup, touchend oder pointerup
  var end_move = function(event) {
    if(pointer_active || touch_active || mouse_active) {
      event.preventDefault();
      pointereventcache = [];
      clear_rect(); 
      if(moved) callback(Math.floor(xs),Math.floor(ys),Math.floor(xe),Math.floor(ye));
      xs = 0;
      ys = 0;
      xe = roi_width;
      ye = roi_height;
      pointer_active = touch_active = mouse_active = moved = false;
    }
  } // end_move
  
// Eventhandler für den Start der Aktion bei mousedown, touchstart oder pointerdown
  var start_roi = function(event) {
    event.preventDefault();
    offset = roi.getBoundingClientRect();
    if(can_pointer) {
      pointereventcache.push(event);
      if(pointereventcache.length==1) {
        xs = pointereventcache[0].clientX - offset.left;
        ys = pointereventcache[0].clientY - offset.top;
      }
      pointer_active = true;
    }
    else if(event.targetTouches && event.targetTouches.length) {
      if(event.targetTouches.length==1) {
        xs = event.targetTouches[0].clientX - offset.left;
        ys = event.targetTouches[0].clientY - offset.top;
      }
      touch_active = true;
    }
    else {
      xs = event.clientX - offset.left;
      ys = event.clientY - offset.top;
      mouse_active = true;
    }
  } // start_roi
  
// Die Eventhandler für pointer-, touch- und mouse-Events setzen
  if(can_pointer) { 
    roi.style.touchAction = "none";
    roi.addEventListener("pointerdown",start_roi,false);
    roi.addEventListener("pointermove",move_pointer,false);
    roi.addEventListener("pointerup",end_move,false);
  }
  else {
    if(can_touch) {
      roi.addEventListener("touchstart",start_roi,false);
      roi.addEventListener("touchmove",move_touch,false);
      roi.addEventListener("touchend",end_move,false);
    }
    roi.addEventListener("mousedown",start_roi,false);
    roi.addEventListener("mousemove",move_mouse,false);
    roi.addEventListener("mouseup",end_move,false);
  }
  
} // get_roi
    </script>

  </head>

  <body>
    <h1>Region of Interest <br>Ziehen mit Maus, Touch und Pointer</h1>
    <main>
      <p>Im Bild unter diesem Text kann man mit der Maus oder mit ein oder zwei Fingern ein Rechteck aufziehen.</p>    
      <figure id="roi_target"></figure>
    </main>
  </body>
</html>

SVG Zoom[Bearbeiten]

Im letzten Beispiel werden die ermittelten Rechteckkoordinaten verwendet, um eine SVG auf den markierten Bereich zu zoomen. Dazu werden die Viewport-Werte der SVG entsprechend geändert. Um per Javascript über die Eigenschaft viewBox.baseVal auf die Viewportwerte x, y, width und height zugreifen zu können, muss die SVG über das object-Element eingebunden sein. Damit das figure-Element die Größe des in ihm liegenden Objects annimmt, erhält es die CSS-Eigenschaft display: table.

Um die SVG auch noch um die Mitte vergrößern und verkleinern zu können, und um auch die Originalgröße wieder einstellen zu können, werden im HTML noch drei Buttons angelegt.

Das folgende Beispiel zeigt das angepasste HTML und die Methode svg_zoom:

Beispiel ansehen …
<!DOCTYPE html>
<html lang="de">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>RoI - SVG-Zoom</title>
    <style type="text/css">
      #roi_target { display: table; padding:0; border:1px dotted black; }
    </style>

    <script>
"use strict";

window.addEventListener("load",function() {

  // Zoomen eines SVG
  var svg_zoom = function(id_zoom_ele, id_zoomin_button, id_zoomout_button, id_orginalgroesse_button) {
  // id_zoom_ele: ID des Objects mit dem SVG
  // id_zoomin_button : ID des Zoom-In-Buttons
  // id_zoomout_button : ID des Zoom-Out-Buttons
  // id_orginalgroesse_button: ID des Originalgröße-Buttons
    var img = document.querySelector("#" + id_zoom_ele);
    if(!img) {
      console.error("Element " + id_zoom_ele + " nicht gefunden."); 
      return null; 
    }
    var svgDoc = img.contentDocument.querySelector("svg");
    if(!svgDoc) { console.error("Im Element " + id_zoom_ele + " kein SVG gefunden."); return null; }
    // Original-Viewportwerte abragen
    var ox0 = svgDoc.viewBox.baseVal.x;
    var oy0 = svgDoc.viewBox.baseVal.y;
    var owidth = svgDoc.viewBox.baseVal.width;
    var oheight = svgDoc.viewBox.baseVal.height;
    // Größe des SVG im Browser
    var width = img.offsetWidth;
    var height = img.offsetHeight;
    // Skalierung
    var xfaktorprodukt = owidth/width;
    var yfaktorprodukt = oheight/height;

    // Eventhandler für für den Originalgröße-Button
    if( document.querySelector("#" + id_orginalgroesse_button) ) document.querySelector("#" + id_orginalgroesse_button).addEventListener("click",function() {
      xfaktorprodukt = owidth/width;;
      yfaktorprodukt = oheight/height;
      svgDoc.viewBox.baseVal.width = owidth;
      svgDoc.viewBox.baseVal.height = oheight;
      svgDoc.viewBox.baseVal.x = ox0;
      svgDoc.viewBox.baseVal.y = oy0;  
    });

    // Eventhandler für den Zoom-Out-Button
    if( document.querySelector("#" + id_zoomout_button) ) document.querySelector("#" + id_zoomout_button).addEventListener("click",function() {
      zoom(-width/2,-height/2,3*width/2,3*height/2);
    });

    // Eventhandler für den Zoom-In-Button
    if( document.querySelector("#" + id_zoomin_button) ) document.querySelector("#" + id_zoomin_button).addEventListener("click",function() {
      zoom(width/4,height/4,3*width/4,3*height/4);
    });

    // Die Zoomfunktion
    var zoom = function(xs,ys,xe,ye) {
      svgDoc.viewBox.baseVal.x += xs * xfaktorprodukt;
      svgDoc.viewBox.baseVal.y += ys * yfaktorprodukt;  
      var xfaktor = (xe-xs)/width;
      var yfaktor = (ye-ys)/height;
      xfaktorprodukt *= xfaktor;
      yfaktorprodukt *= yfaktor;
      svgDoc.viewBox.baseVal.width  *= xfaktor;
      svgDoc.viewBox.baseVal.height *= yfaktor;
    }

    return zoom;
  } // svg_zoom

  var zoom = svg_zoom("selflogo", "zp", "zm", "z0"); 
  
  get_roi("roi_target", function(xs,ys,xe,ye) { 
    var t;
    if(xe<xs) { t = xs; xs = xe; xe = t; }
    if(ye<ys) { t = ys; ys = ye; ye = t; }
    zoom(xs,ys,xe,ye);
  });

},false);


// Bei einem Element mit Maus oder Touch einen rechteckigen Bereich (Region of Interest) auswählen
"use strict";
var get_roi = function(ele,callback) { 
   ...
} // get_roi
    </script>

  </head>

  <body>
    <h1>Region of Interest - SVG-Zoom</h1>
    <main>
      <p>Im Bild unter diesem Text kann man mit der Maus oder mit ein oder zwei Fingern ein Rechteck aufziehen. Das Bild wird dann auf dieses Rechteck gezoomt.</p>    
      <figure id="roi_target"><object data="selfwikilogo.svg" type="image/svg+xml" id="selflogo" width="240" height="267"></object></figure>
      <button type="button" id="z0">Originalgröße</button>
      <button type="button" id="zp">Vergrößern</button>
      <button type="button" id="zm">Verkleinern</button>
    </main>  
  </body>
</html>

Siehe auch[Bearbeiten]