info@a-coding-project.de

JavaScript-Beispiele: DHTML mit Drag & Drop

Mit DHTML lassen sich feine Dinge anstellen. Ob Aufklapplisten, Blitze oder Drag and Drop - der Fantasie sind keine Grenzen gesetzt.

Drag and Drop

Eine der vielen Anwendungsmöglichkeiten für DHTML sind sogenannte Drag & Drop Funktionen. Drag & Drop bedeutet, der Benutzer kann ein Element mit der Maus anfassen, es beliebig durch die Gegen ziehen und dann wieder fallen lassen.

Wie bei allen DHTML-Dingen benötigen wir auch hierfür eine Browserunterscheidung. Das Prinzip des Drag & Drop ist dann folgendes: Greift der Besucher nach einem Layer (onmousedown-Ereignis), verankern wir es mit der Maus. Verankern bedeutet, sobald sich die Maus bewegt (onmousemove-Ereignis), muss sich auch der Layer bewegen. Derartiges haben wir im Grunde schon in einem vorhergehenden Abschnitt mit Bildern gemacht - nur eben so, dass das Bild immer der Maus folgt. Läßt der Benutzer die Maustaste dann wieder los (onmouseup-Ereignis), wird auch die Verankerung gelöst.

Schritt 1: Einen Layer greifen

Wir wollen uns erst mal die fertige Funktion zum Aufnehmen ansehen (Browserabfrage vorausgesetzt):

var hatgenommen = false;
var posx = 0,posy = 0;
var ziel = new Object();

function Nimm(e)
{
 if(hatgenommen == true){return false;}

 if(browser == 'IE')
 {x=window.event.x; y=window.event.y; t=window.event.srcElement;}
 else if(browser == 'OP')
 { x = e.clientX; y = e.clientY; t = e.target;}
 else if(browser == 'NN6')
 { x = e.clientX; y = e.clientY; t = e.currentTarget;}
 else if(browser == 'NN4')
 { 
  x = e.pageX; y = e.pageY;
  //ziel suchen
  gefunden = false;
  for(i=0;i<document.layers.length;i++)
  {
   a = document.layers[i];
   if(x <= (a.left+a.clip.width) && x >= (a.left) &&
      y <= (a.top+a.clip.height) && y >= (a.top))
   { 
    if(gefunden == false){t = a;}
    else if(t.zIndex < a.zIndex){t = a;} 
   }
  }
 }
 ziel = t;

 if(browser == 'IE' || browser == 'NN6' || browser == 'OP')
 {
  posx = (x - parseInt(t.style.left));
  posy = (y - parseInt(t.style.top));
 }
 else if(browser == 'NN4')
 { posx = (x - t.left); posy = (y - t.top); }

 hatgenommen = true;
}

Um mehrere Layer auf einer Seite verwenden zu können, versuchen wir alles so allgemein wie möglich zu halten. Wir definieren zuerst 4 Variablen. hatgenommen ist ein Boolean mit dem wir klären können, ob der Layer gerade angefaßt wird (true) oder nicht (false). Die Variablen posx und posy brauchen wir um die Verschiebung des Layers angenehmer zu gestalten: Sie beinhalten den horizontalen bzw. vertikalen Abstand der Maus zur linken oberen Ecke. Die Variable ziel stellt den Layer dar, der gerade angefaßt wurde - wir brauchen sie um nicht für jeden Layer eine Funktion schreiben zu müssen oder ständig ID's zu übergeben. Innerhalb der Funktion wird als erstes geprüft, ob der Layer schon gegriffen ist, damit eventuellen Fehler vorgebeugt wird. Da die Browser verschiedene Modelle des Ereignis-Objekts besitzen verallgemeinern wir bis zur Zeile "ziel = t;" die x- und y-Koordinate des Ereignisses sowie das Ziel. Anschließend werden die Abstände berechnet, die zwischen Mauscursor und Layerecke liegen (eben die Variablen posx und posy).

Schritt 2: Den Layer bewegen

Anschließen muss der Layer natürlich bewegt werden. Um alles allgemein zu halten, haben wir den Layer zuvor in der Variablen ziel gespeichert. Die Funktion zum Bewegen könnte dann so aussehen:

function Bewege(e)
{
 if(hatgenommen == false){return false;}

 if(browser == 'IE'){ x = window.event.x; y = window.event.y;}
 else if(browser == 'NN6') { x = e.clientX; y = e.clientY;}
 else{ x = e.pageX; y = e.pageY; }

 x = x-posx;
 y = y-posy;

 if(browser == 'OP')
 {ziel.style.left = x; ziel.style.top = y;}
 else if(browser == 'IE' || browser == 'NN6')
 {ziel.style.left = x+'px'; ziel.style.top = y+'px';}
 else if(browser == 'NN4')
 {ziel.left = x; ziel.top = y;}
}

Auch hier verallgemeinern wir als erstes die Koordinaten des Ereignisses und ziehen dann den Abstand der Maus zur oberen linken Ecke ab. Letztlich brauchen wir das Ziel nur neu zu positionieren.

Schritt 3: Den Layer wieder los lassen

Um den Layer wieder los zu lassen braucht es nicht viel:

function LassLos(){ hatgenommen = false; }

Ist die Variable hatgenommen gleich false wird automatisch die Funktion Bewege abgebrochen - der zuvor bewegte Layer also nicht weiter bewegt.

Schritt 4: Alles Zusammenfügen

Um alles komplett zu machen müssen nun nur noch alle Layer mit Event-Handlern verknüpft werden. Auch hierfür schaffen wir uns eine allgemeine Funktion um bei vielen Layern Übersicht und Platz zu sparen. Die Funktion könnte z.B. so aussehen:

function Verknuepfe(id)
{
 if(browser == 'OP' || browser == 'NN6')
 {
  document.getElementById(id).onmousedown = Nimm;
  document.getElementById(id).onmousemove = Bewege;
  document.getElementById(id).onmouseup = LassLos;
 }
 else if(browser == 'IE')
 {
  document.all[id].onmousedown = Nimm;
  document.all[id].onmousemove = Bewege;
  document.all[id].onmouseup = LassLos;
 }
 else if(browser == 'NN4')
 {
  document[id].captureEvents(Event.MOUSEMOVE | Event.MOUSEDOWN 
                             | Event.MOUSEUP);
  document[id].onmousedown = Nimm;
  document[id].onmousemove = Bewege;
  document[id].onmouseup = LassLos;
 }
}

Beim Laden der Seite braucht nun lediglich noch die Verknüpfung mit den Layern hergestellt werden. Der gekürzte HTML-Code dazu könnte letztlich dann z.B. so aussehen:

<html>
<head>
<title>Drag & Drop</title>
<script type="text/javascript">
<!--
 ua = navigator.userAgent.toLowerCase();
 uv = parseInt(navigator.appVersion);
 if(ua.indexOf('opera') != -1 && uv >= 4){browser = 'OP'}
 else if(ua.indexOf('msie') != -1 && uv >= 4){browser = 'IE'}
 else if(uv == 4){browser = 'NN4'}
 else if(uv >= 5){browser = 'NN6'}

 // ... gekürzt ...
//-->
</script>
</head>
<body onload="Verknuepfe('Layer1'); Verknuepfe('Layer2')">

 <div id="Layer1" style="position:absolute; left:10px; top:10px;
 width:100px; height:100px;"> Verschiebe mich!</div>

 <div id="Layer2" style="position:absolute; left:200px; op:200px; 
width:100px; height:100px;">Verschiebe mich!</div>

</body>
</html>

Ausklapplisten mit dem DOM

Das folgende Beispiel beschreibt Aufklapplisten nach dem DOM, d.h. nicht bzw. nur teilweise DOM-fähige Browser wie der Netscape 4.x oder Opera 4.x können damit leider nur wenig anfangen.

Vorbereitung: Die Symbole anfertigen

Das Prinzip ist kurz erklärt: Klickt der User auf einen Listenknoten (ul-Element) der Unterknoten besitzt klappen dies Unterknoten auf bzw. zu. Letztlich ergibt sich eine Art Baumnavigation wie man sie z.B. vom Windows-Explorer kennt:

Das Listen-Menü des Windows-Explorers
Darstellung: Das Listen-Menü des Windows-Explorers

Entsprechend brauchen wir also als erstes einige Bildchen. Ein Screenshot des Explorers und ein bißchen Grafikbearbeitung schon haben wir die gewünschten Grafiken erstellt:

Die ausgeschnittenen Listenbilder
Darstellung: Die ausgeschnittenen Listenbilder

Bevor wir mit JavaScript ans Werk gehen können, sollten wir das HTML-Grundgerüst erstellen. Wir benötigen eine Liste mit mehreren Unterlisten. Jedem Listeneintrag wird außerdem eine CSS-Klasse (class-Attribut) zugewiesen. Innerhalb der CSS-Definitionen können wir dann die normalen Listensymbole (Punkt, Kreis, Quadrat, ...) gegen unsere Bilder eintauschen. Die list-style-image-Eigenschaft übernimmt diese Aufgabe für uns:

<html>
<head>
<title>Ausklappliste</title>
<style type="text/css">
<!--
 .liste1
  {
   list-style-image: url('knopf_normal.jpg');
   padding-left: 0px; padding-top: 0px; padding-bottom: 0px;
   margin-left: 9px; margin-top: 0px; margin-bottom: 0px;
   font-size:12px; font-weight:normal;
  }
 .liste2
  {
   position:relative; left:0px; top:0px;
   list-style-image: url('knopf_minus.jpg');
   padding-left: 0px; padding-top: 0px; padding-bottom: 0px;
   margin-left: 9px; margin-top: 0px; margin-bottom: 0px;
   font-size:12px; font-weight:bold;
  }
 .liste3
  {
   position:relative; left:0px; top:0px;
   list-style-image: url('knopf_normal.jpg');
   padding-left: 10px; padding-top: 0px; padding-bottom: 0px;
   margin-left: 30px; margin-top: 0px; margin-bottom: 0px;
   font-size:12px; font-weight:bold;
  }
-->
</style>
<script type="text/javascript">
<!--

//-->
</script>
</head>
<body>
<ul class="liste3">
 <li class="liste1">&nbsp;&nbsp;Home</li>
 <li id="a1" class="liste2">&nbsp;&nbsp;Angebote
  <ul id="a2" class="liste2">
   <li class="liste1">&nbsp;&nbsp;Aktuelle Angebote</li>
   <li id="aa1" class="liste2">&nbsp;&nbsp;Pflanzen
    <ul id="aa2" class="liste2">
     <li class="liste1">&nbsp;&nbsp;Blumen</li>
     <li class="liste1">&nbsp;&nbsp;Bäume</li>
    </ul>
   </li>
   <li class="liste1">&nbsp;&nbsp;Gartenbedarf</li>
  </ul>
 </li>
 <li class="liste1">&nbsp;&nbsp;Kontakt</li>
 <li class="liste1">&nbsp;&nbsp;Suche</li>
</ul>
</body>
</html>

Wie Sie am obigen Beispiel sehen können wurden schon ganz bestimmte Einträge mit ID's und anderen Klassen versehen. Das Schema sollte recht leicht nachvollziehbar sein: Alle normalen Einträge bekommen die Klasse liste1, alle Einträge die als Haupteintrag für Untereinträge sein sollen bekommen die Klasse liste2 und der Haupteintrag der gesamten Liste bekommt die Klasse liste3. Außerdem bekommen alle Übereinträge (also die, die Unterknoten enthalten) eine ID die sich auch im nächsten ul-Element widerspiegelt: Heißt der Übereintrag z.B. a1, so heißt das untergeordnete ul-Element a2; heißt er b1, so heißt das ul-Element b2 usw. Diese Einteilung wurde natürlich nicht ohne Grund so vorgenommen. Klickt der User auf einen Haupteintrag, können wir leicht über dessen ID die ID des Untereintrags heraus finden indem die letzte Zahl weggestrichen und dafür eine 2 angehängt wird.

Die Zweige mit JavaScript verbinden

Als erstes müssen alle Haupteinträge mit einem Ereignis verknüpft werden. Eine kleine Funktion hilft uns unnötige Schreibarbeit bei vielen Knoten zu sparen (die Browserabfrage muss natürlich mit rein - die sparen wir uns jetzt aber mal):

function Verknuepfe(id)
{
 if(browser == 'OP' || browser == 'NN6')
 { a = document.getElementById(id); }
 else if(browser == 'IE')
 { a = document.all[id]; }
 a.onmousedown = Click;
 a.style.listStyleImage = 'url('knopf_minus.jpg')';
}

Im obigen HTML-Beispiel haben wir ja zwei Hauptknoten definiert. Damit beide mit der Funktion Click verknüpft werden, fügen wir deshalb dem body-Tag einen kleinen Eintrag hinzu:

<body onload="Verknuepfe('a1'); Verknuepfe('aa1'); ">

Die Zweige auf und zu klappen

Nun fehlt also nur noch die Funktion Click mit der wir die Unterknoten auf und zu klappen. Wir wollen Sie uns erstmal fertig anschauen:

var list = '';

function Click(e)
{
 if(browser == 'IE'){ t = window.event.srcElement; }
 else if(browser == 'OP'){ t = e.target; }
 else if(browser == 'NN6'){ t = e.currentTarget; }

 //kinder finden
 n = t.id.substr(0,t.id.length-1);
 verstecken = false;
 if(list.indexOf(n+',') == -1){verstecken = true; list += n+',';}
 else
 {
  list = list.substr(0,list.indexOf(n+','))+
         list.substr(list.indexOf(n+',')+n.length+1,list.length);
 }

 if(browser == 'OP' || browser == 'NN6')
 {a = document.getElementById(n+'2'); 
  b = document.getElementById(n+'1');}
 else if(browser == 'IE')
 {a = document.all[n+'2']; b = document.all[n+'1'];}

 if(verstecken == true)
 {
  a.style.visibility = 'hidden'; a.style.display = 'none';
  b.style.listStyleImage = 'url('knopf_plus.jpg')';
 }
 else
 {
  a.style.visibility = 'visible'; a.style.display = 'block';
  b.style.listStyleImage = 'url('knopf_minus.jpg')';
 }

 if(browser == 'NN6'){e.cancelBubble = true; e.bubbles = false;}
 else if(browser == 'IE'){window.event.cancelBubble = true;}
 return false;
}

Als erstes steht dort die Variable list, sie gibt uns auskunft darüber welche Knoten schon eingeklappt sind und welche nicht. Innerhalb der Funktion wird als erstes das Ziel-Objekt herausgefiltert und in t gespeichert. Nun müssen wir die ID des Elements herausfinden auf das geklickt wurde und welche ID entsprechend seine Unterknoten besitzen (Sie erinnern sich: Die Sache mit der 1 und 2 am Ende). Gleichzeitig prüfen wir, ob die ID schon in list enthalten ist (und entfernen sie) oder ob sie noch nicht enthalten ist (dann fügen wir sie hinzu). Damit haben wir auch geprüft ob die Liste auf oder zu zu klappen ist - die Variable verstecken speichert dazu den entsprechenden Wert true oder false. Nun kann es an die eigentliche Funktion gehen: Auf- und Zuklappen. Zuerst eine Verallgemeinerung um Platz zu sparen und dann wird je nach Wert von verstecken die Liste ein- bzw. ausgeblendet indem die CSS-Eigenschaft visibility auf hidden (versteckt) bzw. visible (sichtbar) gesetzt wird. Außerdem muss auch die Eigenschaft display mit einem neuen Wert versehen werden, da das Design sonst nicht zusammen rutschen würde und sich da wo zuvor die Einträge waren leere Flächen bilden würden. Letztlich setzen wir die CSS list-style-image-Eigenschaft neu um auch das Listensymbol entsprechen zu verändern (Pluszeichen für aufklappbare Liste und Minuszeichen für eine offene Liste).
Am Schluß müssen wir noch das Event-Bubbling verhindern, da sonst Fehler auftreten könnten. Das fertige Resultat sieht dann schon recht nett aus ...

Blitze auf der Seite

Das nun folgende Beispiel vereint die Techniken der zwei vorangegangenen Beispiele in einem: Positionieren von Layern und deren Sichtbarkeit bestimmen. Wir hier nämlich versuchen Blitze auf unsere Homepage zu zaubern. Die vier dafür notwendigen Grafiken haben wir recht schnell mit einem guten Grafikprogramm erstellt:

Die 4 Blitze als Grafiken
Darstellung: Die 4 Blitze als Grafiken

Das HTML-Grundgerüst

Jeder dieser vier Grafiken binden wir als Layer in das Dokument ein. Da die Blitze am besten bei Nacht wirken, entscheiden wir uns hier für einen schwarzen Seitenhintergrund. Das HTML-Gerüst:

<html>
<head>
<title>Blitze auf der Seite</title>
<script language="JavaScript">
<!--
 ua = navigator.userAgent.toLowerCase();
 uv = parseInt(navigator.appVersion);
 if(ua.indexOf('opera') != -1 && uv >= 4){browser = 'OP'}
 else if(ua.indexOf('msie') != -1 && uv >= 4){browser = 'IE'}
 else if(uv == 4){browser = 'NN4'}
 else if(uv >= 5){browser = 'NN6'}

 // ... hier weiter ...

//-->
</script>
<style type="text/css">
<!--
 .blitz{position:absolute; left:0px; top:0px; visibility:hidden;}
-->
</style>
</head>
<body text="white" bgcolor="black" onload="StartBlitze()" >

<div id="blitz1" class="blitz">
 <img border="0" src="blitz1.gif" >
</div>
<div id="blitz2" class="blitz">
 <img border="0" src="blitz2.gif" >
</div>
<div id="blitz3" class="blitz">
 <img border="0" src="blitz3.gif" >
</div>
<div id="blitz4" class="blitz">
 <img border="0" src="blitz4.gif" >
</div>

<h1>Blitze</h1>

 ... Seiteninhalt ...
</body>
</html>

Die Blitze erzeugen - Zufallsreihenfolgen festlegen

Als nächstes machen wir uns an das eigentliche Skript und vor allem an die Funktion, die die Blitze positionieren und anzeigen soll. Um die Blitze nicht einfach irgendwo zu positionieren sondern im Hauptteil der Seite, wird der ungefähre Anzeigebereich berechnet. Die Eigenschaften screen.availHeight und screen.availWidth geben uns darüber Aufschluß, wie breit das Browserfenster maximal sein dürfte. Daraus lassen wir eine zufällige Position ermitteln - wie zuvor auch schon benutzt, hilft hier die Methode Math.random weiter. Außerdem wählen wir ebenso zufällig einen der vier Layer aus. Der Rest ist einfach und sollte soweit bekannt sein: Der ausgewählte Layer wird auf die errechnete Position gesetzt und angezeigt. Hier der Code dazu:

var blitze_timer = false;
var blitzIDs = new Array('blitz1','blitz2','blitz3','blitz4');

function DoBlitz()
{
 i = Math.round(Math.random()*(blitzIDs.length-1));
 h = screen.availHeight-100;
 w = screen.availWidth-100;
 l = Math.round(Math.random()*(w));
 t = Math.round(Math.random()*(h));
 if(browser == 'NN4')
 {
  a = document[blitzIDs[i]];
  a.visibility = 'show'; a.left = l; a.top = t;
 }
 else if(browser == 'NN6' || browser == 'OP')
 {
  a = document.getElementById(blitzIDs[i]);
  a.style.visibility = 'visible'; a.style.display = 'block';
  if(browser == 'NN6'){a.style.left=l+'px'; a.style.top=t+'px';}
  else if(browser == 'OP'){a.style.left=l; a.style.top=t;}
 }
 else if(browser == 'IE')
 {
  a = document.all[blitzIDs[i]];
  a.style.visibility = 'visible'; a.style.display = 'block';
  a.style.left = l+'px'; a.style.top = t+'px';
 }
 window.setTimeout('ClearBlitze()',200);
}

Wie Sie eventuell schon bemerkt haben, wurden die ID's der Layer im Array blitzIDs gespeichert - JavaScript kann so leichter einen zufälligen Layer auswählen.

Die Blitze entfernen

Nun fehlt eigentlich nicht mehr viel. Die obige Funktion DoBlitz ruft nun ja zum Entfernen der Blitze die Funktion ClearBlitze auf. Sie braucht eigentlich nicht viel zu machen als alle Layer durch zu gehen und diese verschwinden zu lassen. Sind alle Blitze wieder unsichtbar wird einfach mit einem zufälligen Timeout ein neuer Blitz gestartet:

function ClearBlitze()
{
 for(i=0;i<blitzIDs.length;i++)
 {
  if(browser == 'NN4')
  { document[blitzIDs[i]].visibility = 'hide'; }
  else if(browser == 'NN6' || browser == 'OP')
  {
   a = document.getElementById(blitzIDs[i]);
   a.style.visibility = 'hidden';
   a.style.display = 'none';
  }
  else if(browser == 'IE')
  {
   document.all[blitzIDs[i]].style.visibility = 'hidden';
   document.all[blitzIDs[i]].style.display = 'none';
  }
 }

 if(blitze_timer == true)
 {
  s = Math.round(Math.random()*(20));
  window.setTimeout('DoBlitz()',s*100)
 }
}

Um die Blitzerei nun zu starten und eventuell wieder stoppen zu können bedarf es lediglich zweier kleiner Funktionen - und schon ist das Skript komplett fertig:

function StartBlitze(){ blitze_timer = true; DoBlitz(); }
function StopBlitze(){ blitze_timer = false; }

Um die Blitze automatisch zu starten haben wir im oben vorgestellten HTML-Grundgerüst schon ein onload-Ereignis in den body eingebunden. Wenn Sie wollen können Sie die Blitze noch mit Sounds hinterlegen - wie das geht wurde in einem vorhergehenden Beispiel ja schon beschrieben.

Über uns

Stefan Wienströer

Wir entwickeln Webanwendungen mit viel Leidenschaft. Unser Wissen geben wir dabei gerne weiter. Mehr über a coding project

Auch interessant