JavaScript/Tutorials/Mouse and More
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.
Inhaltsverzeichnis
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:
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:
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:
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.
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.
<!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.
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.
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.
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.
// 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);
}
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:
<!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 <SPACE><br>Ende mit <RET><br>Abruch mit <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
:
<!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
- Mouse, Touch und Pointer-Events
- Positionsabfrage
- Tastaturevents
- Position eines Elements relativ zum Viewport
- SVG
- Zugriff auf den Viewport:
- stackoverflow: how-to-parse-svg-elements-viewbox-x-y-width-and-height-values
- Weitere Quelle waren die Javascriptconsole und console.info