JavaScript/Tutorials/Mouse and More

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

Seit dem Aufkommen von Geräten mit Touch-Screen und auch ohne Maus reicht es nicht mehr, sich nur um Mouse-Events zu kümmern. Zwar emulieren diese Geräte das click-Event, aber bei Aktionen wie "Ziehen" oder Multitouch müssen weitere Events berücksichtigt werden.

Als erstes kamen die drei Touch-Events dazu. Diese Events werden von den meisten Browsern unterstützt. Da diese Events nur auf Touch-Aktionen reagieren, musste man sowohl für Maus- als auch für Touch-Aktionen Eventhandler anlegen.

Dieses Problem sollte durch die Pointer-Events beseitigt werden. Diese Events reagieren auf Mouse- und auf Touch-Aktionen und es gibt zu jedem Maus- ein Pointer-Event. Die Browserunterstützung ist ebenfalls sehr gut.

Die Event-Modelle für Touch- und Maus-Aktionen

Mouse-Events

Werden ausgelöst, wenn die linke Maustaste gedrückt bzw. losgelassen wird.

Werden ausgelöst, wenn die Maus ein Element überfährt oder verlässt. Die Events mouseover und mouseout bubblen. Wenn Sie über ein Element im Element fahren, werden diese Events ausgelöst. Bei mouseenter und mouseleave ist das nicht der Fall.

Wird ausgelöst, wenn die Maus bewegt wird. Im Testscript wird die Anzeige der Koordinaten mit mousedown eingeschaltet, und mit mouseup abgeschaltet.

Die Koordinaten der Maus bei Eintritt des Events können mit folgendem Code abgefragt werden:

Ermitteln der Mauskoordinaten
let offset = mtp.getBoundingClientRect();
let x = event.clientX - offset.left;
let y = event.clientY - offset.top;

Touch-Events

Werden ausgelöst, wenn der Finger oder ein anderes Touch-Device die sensitive Fläche berührt oder gehoben wird.

Wird ausgelöst, wenn der Finger oder ein anderes Touch-Device über die sensitive Fläche gezogen wird. Im Testscript wird die Anzeige der Koordinaten mit touchstart eingeschaltet, und mit touchend abgeschaltet.

Die Koordinaten von ein oder zwei Fingern oder anderer Touch-Devices erhält man mit folgendem Code:

Ermitteln der Touchkoordinaten
let offset = mtp.getBoundingClientRect();
if(event.targetTouches.length>1) {
  let xs = event.targetTouches[0].clientX - offset.left;
  let ys = event.targetTouches[0].clientY - offset.top;
  let xe = event.targetTouches[1].clientX - offset.left;
  let ye = event.targetTouches[1].clientY - offset.top;
}
else {
  let x = event.targetTouches[0].clientX - offset.left;
  let y = event.targetTouches[0].clientY - offset.top;
}

Bei Toch-Events werden die Multitouches im Array event.targetTouches gespeichert. Es ist auch möglich, auf mehr als zwei Touches zu reagieren, die maximale Anzahl der Multitouches ist geräteabhängig. Um zu verhindern, dass beim Ziehen die Seite scrollt, sollte man noch ein event.preventDefault(); in den Eventhandler einbauen.

Pointer-Events

Werden ausgelöst, wenn die linke Maustaste gedrückt bzw. losgelassen wird, bzw. wenn der Finger oder ein anderes Touch-Device die sensitive Fläche berührt oder gehoben wird.

Werden ausgelöst, wenn die Maus, der Finger oder ein anderes Touch-Device ein Element überfährt oder verlässt. Die Events pointerover und pointerout bubblen. Wenn Sie über ein Element im Element fahren, werden diese Events ausgelöst. Bei pointerenter und pointerleave ist das nicht der Fall.

Wird ausgelöst, wenn die Maus bewegt wird, oder wenn der Finger oder ein anderes Touch-Device über die sensitive Fläche gezogen wird. Im Testscript wird die Anzeige der Koordinaten mit pointerdown eingeschaltet, und mit pointerup abgeschaltet.

Die Koordinaten von ein oder zwei Fingern oder anderer Touch-Devices erhält man mit folgendem Code:

Ermitteln der Pointerkoordinaten
let offset = mtp.getBoundingClientRect();
let pointereventcache = [];

// Im Eventhandler für pointerdown
if(event.type=="pointerdown") pointereventcache.push(event);

// Im Eventhandler für pointermove
for (let i = 0; i < pointereventcache.length; i++) {
	if (event.pointerId == pointereventcache[i].pointerId) {
		pointereventcache[i] = event;
		break;
	}
}
if(pointereventcache.length>1) {
	let xs = pointereventcache[0].clientX - offset.left;
	let ys = pointereventcache[0].clientY - offset.top;
	let xe = pointereventcache[1].clientX - offset.left;
	let ye = pointereventcache[1].clientY - offset.top;
}
else {
	let x = pointereventcache[0].clientX - offset.left;
	let y = pointereventcache[0].clientY - offset.top;
}

// Im Eventhandler für pointerup
pointereventcache = [];

Bei den Pointer-Events bekommt jeder Touch eine ID. Um auf vorherige Touches zugreifen zu können, muss man sie speichern. Es ist auch möglich, auf mehr als zwei Touches zu reagieren, die maximale Anzahl der Multitouches ist geräteabhängig. Um zu verhindern, dass beim Ziehen die Seite scrollt, sollte man noch ein touch-action = "none"; setzen.

Auswahl des Modells

Neben Browsern, die nur Maus- und Touchevents berücksichtigen, gibt es auch Browser die Maus- und Pointerevents berücksichtigen, und es gibt Browser, die alle drei Modelle unterstützen.

Mit

const can_pointer_event = ("PointerEvent" in window);
const can_touch_event = ("TouchEvent" in window);

kann überprüft werden, welches Eventmodell der Browser unterstützt. Dabei sollten dann Pointer-Events bevorzugt zum Einsatz kommen, und Touch- und Mouse-Events nur, wenn Pointer-Events nicht unterstützt werden.

Testseite für Maus-, Touch- und Pointer-Events

Mit der Testseite für Maus-, Touch- und Pointerevents können Sie die meisten Mouse-, Touch- und Pointer-Events testen und prüfen, ob und wann welches Event ausgelöst wird.

Beispiel ansehen …
Testseite für Maus-, Touch- und Pointerevents

Tastaturbedienbarkeit

Es gibt auch Seitenbesucher, die Ihre Seite nur mit der Tastatur bedienen können. Achten Sie daher darauf, neben der Berücksichtigung diverser Pointer-Devices Ihre Seite auch tastaturbedienbar zu machen. Die folgende Anwendung zeigt, wie man auch die Tastatur berücksichtigen kann.

Region of Interest

In diesem Kapitel wird als Anwendung der Maus- Touch- und Pointerevents 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. Zusätzlich ist auch die Markierung mit der Tastatur möglich.

Das HTML

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

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 Bereich unter diesem Text kann man mit der Maus oder mit ein oder zwei Fingern ein Rechteck aufziehen. Zur Bedienung mit der Tastatur kann der Bereich mit der Tabulatortaste fokussiert werden.</p>		
      <figure id="roi_target"></figure>
    </main>
  </body>
</html>

Das Javascript

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

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

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.

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

    ...

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

Markieren mit den Touch-Events

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 Finger 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.

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

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.

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;
}

Auswahl der geeigneten Events

Da sich die verschiedenen Eventmodelle stören können, muss ausgewählt werden, welches das geeignete ist. Dazu wird überprüft, welches Modell unterstützt wird, um danach die entsprechenden Events einzurichten. Werden Pointerevents unterstützt, werden Maus- und Touchaktionen über Pointeevents behandelt. Werden nur Touchevents unterstützt, werden Touchaktionen über Touchevents und Mausaktionen über Mausevents behandelt.

Setzen der Eventhandler
// Ermitteln der unterstützten Methoden  
  var can_touch = ("TouchEvent" in window);
  var can_pointer = ("PointerEvent" in window);

// 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);
  }
Hinweis:
Inzwischen unterstützen alle relevanten Browser die Pointerevents. Daher sollten auch nur noch diese verwendet werden.

Markieren mit der Tastatur

Um das Script auch mit der Tastatur bedienen zu können, muss der Bereich mit tabIndex = 0; fokussierbar genacht werden. In den Eventhandlern für focus und blur wird ein Eventhandler für keydown hinzugefügt bzw. wieder entfernt. In diesem Eventhandler wird dann auf die Tasten Pfeil links/rechts/up/down, Escape, Blank und Return reagiert. Zusätzlich wird die Defaultaktion dieser Tasten mit preventDefault() unterdrückt.

Das vollständige Script

Im folgendem Beispiel werden Mouse- Touch- und Pointerevents sowie Tastaturevents 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);

},false);


// Bei einem Element mit Maus oder Touch oder Tastatur einen rechteckigen Bereich (Region of Interest) auswählen
"use strict";
var get_roi = function(ele,callback,preserveAspectRatio) { 
// ele: ID des sensiblen Elements
// callback: Funktion, die nach dem Markieren aufgerufen wird
// preserveAspectRatio: Verzerren möglich (false) oder nicht (true)
  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;
  if(roi_height==0) {
    console.error("Höhe 0 reicht nicht!");
    return;
  }
  var aspectRatio = roi_width/roi_height;
  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

// Rechteck zum Quadrat vergrößern
  var fix_aspectRatio = function() {
    var dx = xe - xs;
    var dy = ye - ys;
    var sdx = dx>0?1:-1;
    var sdy = dy>0?1:-1;
    var adx = dx*sdx;
    var ady = dy*sdy;
    if(adx<ady*aspectRatio) adx = ady*aspectRatio;
    else                    ady = adx/aspectRatio;
    dx = adx*sdx;
    dy = ady*sdy;
    xe = xs + dx;
    ye = ys + dy;
  } // fix_aspectRatio
  
// 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;
      if(preserveAspectRatio) fix_aspectRatio();
      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;
      }
      if(preserveAspectRatio) fix_aspectRatio();
      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;
      }
      if(preserveAspectRatio) fix_aspectRatio();
      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);
  }
  
// Tastatursteuerung
  roi.tabIndex = 0;
  var saved_border = roi.style.border;
  var activecross;
  var info = document.createElement("output"),showinfo,removeinfo;
  info.style.position = "absolute";
  info.style.top = "0px";
  info.style.left = "0px";
  info.style.margin = "10px";
  info.style.backgroundColor = "white";
  info.style.lineHeight = "1.4em";
  info.innerHTML = "Bewegen der Ecken mit den Pfeiltasten (mit Shift schnell)<br>Wechseln der aktiven (roten) Ecke mit &lt;SPACE><br>Ende mit &lt;RET><br>Abruch mit &lt;ESC>";
  info.hidden = true;
  roi.appendChild(info);
  
  // Zeichnen eines Fadenkreuzes
  var draw_crosshair = function(x,y) {
    ctx.beginPath();
    ctx.moveTo(x-15,y);
    ctx.lineTo(x+15,y);
    ctx.moveTo(x,y-15);
    ctx.lineTo(x,y+15);    
    ctx.stroke();
  } // draw_crosshair

  // Zeichnen eines Rechtecks mit Fadenkreuz
  var draw_rect_crosshair = function(xs,ys,xe,ye,active) {
    draw_rect(xs,ys,xe,ye);
    var current_color = ctx.strokeStyle;
    var current_width = ctx.lineWidth;
    ctx.strokeStyle = "red";
    ctx.lineWidth = 3;
    if(active == 0) draw_crosshair(xs,ys);
    else if(active == 1) draw_crosshair(xe,ys);
    else if(active == 2) draw_crosshair(xe,ye);
    else draw_crosshair(xs,ye);
    ctx.strokeStyle = current_color;
    ctx.lineWidth = current_width;
  } // draw_rect_crosshair
  
  // Eventhandler für focus
  var focus = function() {
    roi.style.border = "1px solid red";
    xs = 10; ys = 10; xe = roi_width - 10 ; ye = roi_height - 10;
    activecross = 0;
    draw_rect_crosshair(xs,ys,xe,ye,activecross);
    document.documentElement.addEventListener("keydown", keydown, false);
    showinfo = window.setTimeout(function(){
      info.hidden = false;
    },500);
    removeinfo = window.setTimeout(function(){
      info.hidden = true;
    },12000);
  }
  
  // Eventhandler für blur
  var blur = function() {
    roi.style.border = saved_border;
    clear_rect();
    xs = 0; ys = 0; xe = roi_width ; ye = roi_height;
    document.documentElement.removeEventListener("keydown", keydown, false);
    window.clearTimeout(showinfo);
    window.clearTimeout(removeinfo);
    info.hidden = true;
  }
  
  // Eventhandler für keydown
  var keydown = function(event) {
    var keyCode = event.keyCode;
    if(keyCode && (keyCode==13 || keyCode==27 || keyCode==32 || keyCode==37 || keyCode==38 || keyCode==39 || keyCode==40)) { 
      var d = event.shiftKey?10:1;
      event.preventDefault();
      window.clearTimeout(showinfo);
      window.clearTimeout(removeinfo);
      info.hidden = true;
      switch(keyCode){
        case 37: // links
          if(activecross == 0 || activecross == 3) xs = Math.max(0,xs-d);
          else if(activecross == 1 || activecross == 2) xe = Math.max(0,xe-d);
          break;
        case 38: // rauf
          if(activecross == 0 || activecross == 1) ys = Math.max(0,ys-d);
          else if(activecross == 2 || activecross == 3) ye = Math.max(0,ye-d);
          break;
        case 39: // rechts
          if(activecross == 0 || activecross == 3) xs = Math.min(roi_width,xs+d);
          else if(activecross == 1 || activecross == 2) xe = Math.min(roi_width,xe+d);
          break;
        case 40: // runter
          if(activecross == 0 || activecross == 1) ys = Math.min(roi_height,ys+d);
          else if(activecross == 2 || activecross == 3) ye = Math.min(roi_height,ye+d);
          break;
        case 32: // space
          activecross = (activecross+1)%4;
          break;
        case 13: // return
          if(preserveAspectRatio) fix_aspectRatio();
          if(xs!=xe && ys!=ye) callback(xs,ys,xe,ye);
        case 27: // escape
          blur();
          return;
          break;
      }
      draw_rect_crosshair(xs,ys,xe,ye,activecross);
    }
  }
  
  roi.addEventListener("focus", focus, false);
  roi.addEventListener("blur", blur, false);
  
} // get_roi
    </script>

  </head>

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

SVG Zoom

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 Objekts 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 svgzoom = 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; }
    svgzoom(xs,ys,xe,ye);
  },false);

},false);


// Bei einem Element mit Maus oder Touch oder Tastatur einen rechteckigen Bereich (Region of Interest) auswählen
"use strict";
var get_roi = function(ele,callback,preserveAspectRatio) { 
   ...
} // 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. Zur Bedienung mit der Tastatur kann das Bild mit der Tabulatortaste fokussiert werden.</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