Dynamic HTML (DHTML)

Copyright © 2001 - 2006 Edwin Martin <edwin@bitstorm.nl>

Laatst aangepast: 2 januari 2006.

Voor het goed kunnen volgen van deze cursus is kennis nodig van HTML, JavaScript en Cascading Style Sheets. Een cursus Cascading Style Sheets is hier te volgen:

Onbekende woorden en kleuren kunnen worden opgezocht in deze lijst:

Veel voorbeelden in deze cursus maken gebruik van DHTML. Een browser die DHTML ondersteunt is daarom vereist. Voor het eerste deel kan worden volstaan met Netscape 4 of hoger of Internet Explorer 4 of hoger. In het laatste deel van de cursus wordt op de W3C-standaarden ingegaan, waarvoor minstens Mozilla, Netscape 6 of Internet Explorer 5.5 nodig is.

Update: Deze cursus is geschreven in de tijd dat Netscape 4 en Internet Explorer 4 heel gangbaar waren. Deze browsers worden amper nog gebruikt. Deze cursus is in dat opzicht verouderd. Als introductie tot DHTML blijft deze cursus toch interessant en ook voor het ontwikkelen van backwards-compatible DHTML biedt deze cursus veel informatie. Het W3C-DOM, dat in moderne DHTML wordt gebruikt, komt ook in deze cursus aan bod.

Inleiding

Ooit was het Internet bedacht om informatie uit te wisselen tussen universiteiten en onderzoeksinstellingen. Maar dat is allang niet meer waar het op het Internet om draait. Nu gaat het er bij velen om bezoekers te trekken. En die willen niet alleen informatie lezen, maar ook vermaakt worden.

Helaas is HTML een vrij statische opmaaktaal. Je kunt een pagina wel met wat mooie plaatjes en goede vormgeving wat mooier maken, maak het haalt het nooit bij bijvoorbeeld een multimedia CD-ROM.

Om webpagina's interactief te maken, is er DHTML.

Wat is DHTML?

DHTML staat voor Dynamic HTML en is op zich geen standaard, op de website van het World Wide Web Consortium zullen we tevergeefs naar het begrip 'DHTML' zoeken.

DHTML is eigenlijk een combinatie van standaarden, namelijk:

Voor het kunnen volgen van deze cursus, dient u al bekend te zijn met de eerste drie standaarden.

DOM staat voor Document Object Model en wordt hierna behandeld.

Door bovenstaande technieken samen te gebruiken, is het mogelijk om pagina's interactief te maken waarbij onderdelen verschijnen, veranderen, bewegen en bijvoorbeeld reageren op muiskliks.

Document Object Model (DOM)

Het Document Object Model beschrijft de struktuur van een document en die gebruiken we om bepaalde waarden van HTML-elementen te lezen en te schrijven.

Een eenvoudige DOM

Een HTML-document heeft een zogenaamde 'boomstruktuur'. Het volgende voorbeeld illustreert dat.


<h2>Abonnement</h2>

<form name="mijnform">
<p>Uw e-mail adres:</p>
<input type="text" name="emailadres" value="marlous@sisters.com" size="20">
</form>

In een browser ziet dit er als volgt uit:

Abonnement

Uw e-mail adres:

De struktuur van dit document is als volgt weer te geven:

document --- H2 --- "Abonnement"
         \
          \
           \ FORM (mijnform) --- P --- "Uw e-mail adres:"
                             \
                              \
                               \ INPUT (emailadres) --- value

Kort gezegd komt het er op neer dat het document bestaat uit een kop (H2) en een formulier mijnform (FORM) en dat het formulier bestaat uit een paragraaf (P) en een invoerveld emailadres (INPUT). Het invoerveld heeft een 'eigenschap' value, dat de inhoud van het veld bevat.

Om de waarde van het veld uit te lezen, dienen we de volgende reeks te gebruiken:

document.mijnform.emailadres.value

Door op de volgende knop te klikken zal de inhoud worden getoond:

De boomstruktuur van een document, waarmee de bovenstaande reeks is te maken, is vastgelegd en heet het Document Object Model (DOM).

DOM en Layers

Zolang het formulieren betreft, is de DOM voor alle browsers (met JavaScript) gelijk. Dit 'formulier-DOM' wordt ook wel 'DOM 0' (DOM nul) genoemd). Helaas is het zo, dat als we DHTML willen gebruiken, we te maken hebben met wel vier verschillende DOM's.

Een stukje geschiedenis

Netscape 4.0 was de eerste browser die dynamische HTML ondersteunde. Daarvoor had Netscape de <layer>-tag bedacht. Het was mogelijk om een stuk HTML-code tussen <layer> en </layer> te zetten, waarna het onder andere mogelijk was om deze layer over het scherm te bewegen.

Toen Netscape 4.0 uitkwam, was men bij het World Wide Web Consortium bezig een standaard te bedenken waarmee style sheets als layers worden gebruikt: CSS-Positioning (CSS-P). Gelukkig ondersteunt Netscape deze standaard ook. Omdat CSS-P uiteindelijk de standaard is geworden, zullen we alleen nog CSS-P gebruiken en kunnen we gerust de <layer>-tag weer vergeten. Het is zelfs zo dat Netscape 6 de <layer>-tag helemaal niet meer ondersteunt.

Even na Netscape 4.0 kwam Internet Explorer 4.0 uit. Deze ondersteunde ook CSS-P en de CSS-ondersteuning was veel uitgebreider dan Netscape. Een reden daarvoor is dat World Wide Web Consortium op dat moment ook al veel verder was met het vastleggen van de standaard.

Dit lijkt helaas wat mooier dan het is. Ze ondersteunen beide CSS-P, maar het Document Object Model is verschillend! Hier lag dus een taak voor het World Wide Web Consortium om het Document Object Model vast te leggen. Dit is gelukt en het resultaat, Document Object Model (DOM), wordt door Internet Explorer 5 en Netscape 6 ondersteund.

Helaas is deze DOM weer een beetje anders dan de DOM's van Netscape 4 en Internet Explorer 4. Bij elkaar hebben we dus te maken met vier verschillende DOM's!

  1. DOM 0 - Voor formulieren; werkt in alle browsers met JavaScript-ondersteuning
  2. NS4 DOM - Voor Netscape 4
  3. IE4 DOM - Voor Internet Explorer 4
  4. W3C DOM - De officiële DOM, ondersteund door onder andere Netscape 6, Mozilla en Internet Explorer 5.

Gelukkig kunnen we met wat JavaScript ervoor zorgen dat onze dynamische pagina's er in alle browsers goed uitzien.

CSS-P (Style Sheet-Positioning)

In Netscape 4 en Internet Explorer 4 kunnen we met behulp van style sheets layers definiëren. Een layer is een 'laag', vergelijkbaar met de layer in Photoshop. In een layer kunnen we HTML opnemen. Het maken van een layer is vrij eenvoudig: we nemen de HTML-code op tussen <div>-tags. Door de div ook een id-attribuut te geven, geven we de layer een naam, zodat we die later met JavaScript kunnen veranderen.

Een voorbeeld van een layer:

<div id="mijnlayer">
Deze tekst staan in een layer.
</div>

Zoals we in CSS-cursus hebben gezien, kunnen we aan deze div allerlei stijlkenmerken toekennen, in dit geval een oranje achtergrond:

<style type="text/css">
#mijnlayer {background-color: orange}
</style>

<div id="mijnlayer">
Deze tekst staan in een layer.
</div>

Absolute positionering

Een van de belangrijke kenmerken die DHTML dynamisch maakt, is dat layers over de pagina kunnen bewegen. Dit werkt echter niet zomaar. Om een layer te kunnen verplaatsen, moet deze eerst 'uit de pagina' worden gehaald. We geven de layer dan een vaste plek op de pagina, bijvoorbeeld 300 pixels vanaf de bovenkant en 30 pixels vanaf de linkerkant van de pagina. Dit is alleen mogelijk als we ook de 'position' op 'absolute' zetten. In HTML ziet dit er als volgt uit:

<style type="text/css">
#mijnlayer {
	position: absolute;
	top: 300px;
	left: 30px;
	background-color: orange;
}
</style>

<div id="mijnlayer">
Deze tekst staan in een layer.
</div>

Nu is het mogelijk om met JavaScript de positie van deze layer te veranderen. Zoals eerder is beschreven, is de DOM van Netscape 4 en Internet Explorer 4 verschillend. Het veranderen van de positie is daardoor ook verschillend.

Stel, we willen de x-positie veranderen van 30 pixels in 120 pixels. In Netscape 4 roepen we dan de volgende JavaScript aan:

document.mijnlayer.left=120

In Internet Explorer 4 doen we dat op deze manier:

document.all.mijnlayer.style.left=120

Zo te zien zijn er nogal wat verschillen. Op de volgende pagina zijn voor Netscape 4 en Internet Explorer 4 knoppen geplaatst die bovenstaande JavaScriptjes uitvoeren. Dit voorbeeld werkt alleen in Netscape 4 en Internet Explorer 4 en hoger.

Gelukkig is het mogelijk om in JavaScript een functie te schrijven die dit probleem oplost. Als we dan de positie van een layer willen veranderen, dan roepen we gewoon deze functie aan, en de functie zorgt ervoor dat de juiste JavaScript wordt uitgevoerd. Dit is de functie:

function setPosition( layer, x, y ) {
	if( document.all ) {
		document.all[layer].style.left = x;
		document.all[layer].style.top = y;
	} else {
		document[layer].left = x;
		document[layer].top = y;
	}
}

Vanaf nu kunnen we in Netscape 4 en Internet Explorer 4 de positie veranderen met één JavaScript-aanroep:

setPosition('mijnlayer', 120, 300)

Het volgende voorbeeld werkt nu in beide browsers:

World Wide Web Consortium Document Object Model

Bovenstaande voorbeelden gingen uit van de DOM's van Netscape 4 en Internet Explorer 4. Inmiddels heeft het World Wide Web Consortium (W3C) een DOM ontworpen die nu als standaard geldt.

Het grote verschil met de oudere DOM's is dat in de W3C DOM de layers worden opgevraagd met de JavaScript functie getElementById( layer ). Het verplaatsen van 'mijnlayer' uit het voorbeeld hierboven gebeurt met het W3C DOM als volgt:

obj = document.getElementById( 'mijnlayer' )
obj.style.left = 120;

Weer totaal anders dan de eerdere manieren. Maar ook hier is een zogenaamde 'wrapper' functie voor te schrijven:

function setPosition( layer, x, y ) {
	if( document.getElementById ) {
		obj = document.getElementById( layer )
		obj.style.left = x;
		obj.style.top = y;
	} else if( document.all ) {
		document.all[layer].style.left = x;
		document.all[layer].style.top = y;
	} else {
		document[layer].left = x;
		document[layer].top = y;
	}
}

Deze functie werkt in alle browsers: Netscape 4 en hoger, Mozilla, Internet Explorer 4 en hoger en zelfs in Opera. Het volgende voorbeeld maakt gebruik van bovenstaande functie en werkt nu ook in Mozilla en Netscape 6.

Er zijn nog veel meer eigenschappen die gewijzigd kunnen worden. Als we willen, kunnen we voor elke eigenschap een eigen functie schrijven. Het is natuurlijk veel handiger om een meer generiek functie te schrijven. We nemen bovenstaande functie en halen daar 'left = x' en 'top = y' uit. Aan het resultaat van de functie kunnen we weer de left en top 'toevoegen':

function getStyle( layer ) {
	if( document.getElementById ) {
		return document.getElementById( layer ).style
	} else if( document.all ) {
		return document.all[layer].style
	} else {
		return document[layer]
	}
}

De x-positie veranderen gaat nu op de volgende manier:

getStyle('mijnlayer').left = 120

Onzichtbaar en zichtbaar maken

Soms willen we layers onzichtbaar maken. Hiervoor gebruiken we visibility='visible' en visibility='hidden'. Met behulp van de functie getStyle() kunnen we een layer onzichtbaar maken met:

getStyle('mijnlayer').visibility='hidden'

Zichtbaar maken kan met:

getStyle('mijnlayer').visibility='visible'

Een veelgebruikte toepassing is een layer te maken die in eerste instantie niet te zien is, maar bijvoorbeeld met een muisklik tevoorschijn komt. Dat een layer tijdens en na het laden van de pagina niet mag worden getoond, geven we aan in de style sheet.

De style sheet van ons eerste voorbeeld wordt dan:

<style type="text/css">
#mijnlayer {
	position: absolute;
	top: 300px;
	left: 30px;
	background-color: orange;
	visibility: hidden;
}
</style>

In het volgende voorbeeld wordt dit toegepast.

Clipping

Als uitbreiding op het zichtbaar en onzichbaar maken, kan ook een gedeelte van een layer zichtbaar worden gemaakt. Dit heet clipping.

Bij een afbeelding kunnen we bijvoorbeeld alleen de linkerbovenhoek laten zien. Of een uitsnede in het midden van de afbeelding.

We hebben hier helaas weer te maken met een compatibiliteits-probleem. Met de getStyle()-functie hebben we het DOM-probleem opgelost, maar bij clipping is ook de waarde anders. Netscape 4 gebruikt namelijk een andere manier van clippen.

In Netscape kunnen we de bovenste 50 pixels van een afbeelding 'afknippen' met:

getStyle('mijnlayer').clip.top=50

In alle andere browsers doen we dit met:

getStyle('mijnlayer').clip='rect(50px auto auto auto)'

In dit laatste voorbeeld geven we weer vier waarden op (boven, rechts, onder, links). Bij de zijden die we niet willen afknippen schrijven we auto.

Om dit compatible te maken, schrijven we hiervoor een aparte clip()-functie:

function clip( layer, top, right, bottom, left ) {
	if( document.getElementById || document.all ) {
		getStyle( layer ).clip='rect('+top+' '+right+' '+bottom+' '+left+')'
	} else {
		document[layer].clip.top = top
		document[layer].clip.left = left
		document[layer].clip.bottom = bottom
		document[layer].clip.right = right
	}
}

Ongeacht browser, kunnen we nu de volgende functie aanroepen:

clip('mijnlayer', 80, 310, 300, 180)

Omdat Netscape 4 geen 'auto' kent, zal dat niet in deze browser werken. Zie onderstaand voorbeeld om clipping in werking te zien:

Hoe komen we bij de pixel-afstanden die in de clip-functie worden gebruikt? De volgorde is zoals gebruikelijk in CSS: boven, rechts, onder en links. Belangrijk is ook dat alle vier de afstanden gerekend worden vanaf de linkerbovenhoek. Zie de volgende pagina waarin dat grafisch is weergegeven.

Net als de visibility, kunnen we ook de clipping in een style sheet vastleggen. We voegen dan bijvoorbeeld de volgende regel toe:

clip: rect(80px 310px 300px 180px)

Z-index

Met z-index kunnen we aangeven welke laag boven welke andere moet komen. Voor velen is dit bekend uit grafische programma's als Photoshop. Hoe hoger de z-index hoe hoger het op de 'stapel' met layers ligt. Een layer met een z-index van 10 komt bovenop de layer te liggen met een z-index van 5.

Omdat in JavaScript het minteken (-) aftrekken betekent, kunnen we natuurlijk niet de naam 'z-index' gebruiken. De JavaScript-notatie wordt 'zIndex'. Met gebruik van de getStyle()-functie, wordt de notatie om een z-index van 2 toe te kennen:

getStyle('mijnlayer').zIndex = 2

Bekijk de z-index in actie:

Net als bij de andere voorbeelden is het vaak verhelderend om ook de source van het voorbeeld op te roepen in de browser.

Ook de z-index is in een style sheet op te nemen. De notatie is dan:

z-index: 2;

Achtergrondkleur veranderen

Het veranderen van de achtergrondkleur is ook geen probleem. Behalve dat het kleur-attribuut bij Netscape 4 afwijkt: deze is namelijk bgColor, terwijl backgroundColor gebruikelijk is, want deze is afgeleid van de CSS-eigenschap background-color.

Door de getStyle-functie te nemen en deze aan te passen, lossen we dit eenvoudig op:

function setBackgroundColor( layer, color ) {
	if( document.getElementById ) {
		document.getElementById( layer ).style.backgroundColor = color;
	} else if( document.all ) {
		document.all[layer].style.backgroundColor = color;
	} else {
		document[layer].bgColor = color;
	}
}

Helaas is het in Netscape 4 niet mogelijk om de voorgrondkleur (de tekstkleur) te wijzigen.

Achtergrondplaatje veranderen

Net als bij de achtergrondkleur, heeft ook bij het veranderen van het achtergrondplaatje Netscape 4 een afwijkend attribuut. Het normale attribuut is backgroundImage, wat is afgeleid van de CSS-eigenschap background-image. Netscape 4 gebruikt het attribuut background.src

Net als bij de achtergrondkleur is dit op te lossen door er een aparte functie voor te maken:

function setBackgroundImage( layer, imageUrl ) {
	if( document.getElementById ) {
		document.getElementById( layer ).style.backgroundImage = "url("+imageUrl+")";
	} else if( document.all ) {
		document.all[layer].style.backgroundImage = "url("+imageUrl+")";
	} else {
		document[layer].background.src = imageUrl;
	}
}

Inhoud wijzigen

Een erg interessante mogelijkheid van DHTML is dat de inhoud van een layer veranderd kan worden. Maar, net als bij een aantal eerdere mogelijkheden, werkt dat in Netscape 4 weer net even anders. Ook hier zullen we dus een wrapper-functie voor moeten schrijven.

function writeText( layer,text ) {
	if( document.getElementById ) {
		document.getElementById( layer ).innerHTML = text;
	} else if( document.all ) {
		document.all[layer].innerHTML = text;
	} else {
		document[layer].document.open();
		document[layer].document.write( text );
		document[layer].document.close();
	}
}

Het veranderen van een layer is dan het eenvoudigweg aanroepen van deze functie:

writeText('mijnlayer', 'Dit is een andere tekst')

De tekst die vervangen wordt kan ook HTML-code bevatten, dus het opnemen van kopjes en bijvoorbeeld afbeeldingen is geen probleem. Wel is het zo dat in Netscape 4 de oorspronkelijke style sheet van de layer verloren gaat.

De beste illustratie is als altijd een werkend voorbeeld:

Helaas is bovenstaande functie writeText niet helemaal in overeenstemming met de W3C-standaarden. De W3C DOM kent helaas niet de eigenschap innerHTML. Gelukkig ondersteunen Internet Explorer en Netscape 6 wel innerHTML.

Volgens de W3C DOM moeten we elk takje of blaadje van de DOM-boom apart verwijderen, toevoegen of vervangen. Om de tekst in een layer te vervangen door de HTML-tekst "<h2>InnerHTML is toch makkelijker</h2>", moeten we eerst alle bestaande 'takken' uit de layer verwijderen, daarna een tak "H2" toevoegen en daaraan een tak met de tekst "InnerHTML is toch makkelijker" toevoegen.

// Vind het layer object
mijnLayerObj = document.getElementById('mijnlayer')
// Wis alles uit de layer
for ( elt=mijnLayerObj.childNodes.length-1; elt>=0; elt-- )
	mijnLayerObj.removeChild(mijnLayerObj.childNodes.item(elt))
// Maak een element "H2"
headingNode = document.createElement("h2")
// Maak een knooppunt met tekst
textNode = document.createTextNode("InnerHTML is toch makkelijker")
// Koppel de tekst aan het H2-kopje
headingNode.appendChild(textNode)
// Voeg het kopje toe aan de layer
mijnLayerObj.appendChild(headingNode)

Wil je weten welke DOM-functies nog meer zijn gedefiniëerd in JavaScript (ECMA Script)? Kijk dan in Appendix E: ECMA Script Language Binding van de W3C DOM specificatie.

Image source

Het volgende hoort eigenlijk niet in deze cursus thuis want het is eigenlijk geen DHTML. Omdat het wel enigszins 'dynamisch' is, is het toch goed om te kennen.

Sinds Netscape 3 is het mogelijk om een plaatje op een pagina te vervangen door een ander plaatje. Je wijzigt als het ware de SRC-attribuut van de IMG-tag.

We hebben bijvoorbeeld de volgende <img>-tag:

<img name="grafiek" src="plaatje.gif">

In JavaScript kunnen we het plaatje als volgt vervangen:

document.grafiek.src = "anderplaatje.gif";

grafiek is hier de waarde van de name-attribuut van de image en "anderplaatje.gif" is de naam van het te tonen plaatje.

Het volgende voorbeeld toont dit op een voorbeeldpagina:

Doordat dit eindelijk enige dynamiek aan de pagina toevoegde, werd (en wordt) deze mogelijkheid erg veel gebruikt om zogenaamde roll-overs te maken. Veel HTML-editors en zelfs grafische programma's zoals FireWorks kennen de mogelijkheid om deze roll-overs eenvoudig te maken.

Het volgende voorbeeld laat de roll-over in werking zien.

Bekijk ook de source-code van dit voorbeeld.

Actie

Alle hierboven genoemde DHTML-mogelijkheden werken in alle recente browsers die DHTML ondersteunen, dus Netscape 4 en hoger, Mozilla, Internet Explorer 4 en hoger en in veel gevallen ook Opera.

Nu gaan we de mogelijkheden bekijken van hoe we deze cross-browser DHTML interactief kunnen maken.

Netscape ondersteunt niet het klikken op een layer en ook geen mouse-overs over een layer. Men moet erg creatief zijn om iets interactiefs te maken dat met alle gangbare browsers werkt.

Beweging

Gelukkig ondersteunt Netscape 4 wel het verplaatsen van een layer en samen met de JavaScript-functie setTimeout() kunnen we een layer bewegen. Het is eigenlijk niet echt bewegen, in werkelijkheid zullen we bijvoorbeeld elke tiende seconde de layer een klein stukje opschuiven.

We maken een layer bewegend door een functie te schrijven. Dit is (helaas) geen generieke functie; voor elke beweging schrijven we een aparte functie. Stel, onze layer staat aan de linkerkant van het venster en we willen hem een stuk naar rechts bewegen. Dus we moeten een aantal keer de volgende code aanroepen:

xpositie = xpositie + 5
getStyle('mijnlayer').left = xpositie

Xpositie is hier een variabele die x-positie aangeeft. We verhogen deze met 5, wat betekent dat de layer 5 pixels naar rechts moet bewegen. Het bewegen doen we door xpositie aan het .left-kenmerk toe te kennen.

Als we deze code maar vaak genoeg aanroepen, zal de layer oneindig lang naar rechts bewegen. Hier moeten we een rem opzetten:

if ( xpositie < 300 ) {
	xpositie = xpositie + 5
	getStyle('mijnlayer').left = xpositie
}

De layer wordt nu alleen verschoven zolang xpositie kleiner is dan 300.

Om deze code elke tiende van een seconde aan te laten roepen, maken we een function heen() met deze code en zorgen we ook dat met setTimeout() de function heen() zichzelf steeds na 100 milliseconden aanroept:

function heen() {
	if ( xpositie < 300 ) {
		xpositie = xpositie + 5
		getStyle('mijnlayer').left = xpositie
		setTimeout( 'heen()', 100 )
	}
}

Bekijk het volgende voorbeeld en bekijk vooral ook de broncode van dit voorbeeld.

Dit voorbeeld bevat een fout: als we op de terug()-knop klikken voordat de layer helemaal rechts is, zal zowel de heen() als de terug()-functie tegelijk gaan werken, wat tot een layer leidt die niet precies weet welke kant hij op wilt.

Een oplossing hiervoor is het klokje van de setTimeout()-functie te wissen zodra op een klok geklikt wordt. Het klokje wordt teruggeven door de setTimeout()-functie:

klokje = setTimeout( 'heen()', 100 )

Met de functie clearTimeout( klokje ) stoppen we de klok. Voor het gemak maken we hier een aparte functie stop() van:

function stop() {
	clearTimeout( klokje )
}

Bekijk nu het volgende voorbeeld, waarin de klok steeds wordt gestopt als we op de heen() of terug()-knop klikken. Er is ook een stop()-knop toegevoegd. Nu kunnen we zonder problemen achter elkaar op de heen(), terug() en stop()-knop klikken.

Bestudeer ook de broncode.

Framerate

Waarom kiezen we in de voorbeelden voor 10 beeldjes per seconde? We weten dat dat een film pas bij 25 beeldjes per seconde voor het oog soepel verloopt. Het probleem is dat de wat langzamere computers deze snelheid niet halen. Het gevolg is dan dat de animatie op een snelle computer sneller afspeelt dan op een langzame computer. Door 10 beeldjes per seconde te gebruiken, kunnen ook de oudere computers de animatie bijhouden en speelt de animatie zich op alle computers met de zelfde snelheid af. Het hangt natuurlijk ook af van de complexiteit van de pagina: een computer heeft meer moeite met een pagina met honderd bewegende elementen dan met één bewegend element. Het beste is om een animatie te testen op zowel een supersnelle computer als op een computer van een paar jaar oud.

Op de volgende pagina kan de framerate worden getest. Bedenk wel dat de framerate afhankelijk is van zowel de snelheid van de computer als de gebruikte browser. Ook is in deze test maar één bewegende laag. Een DHTML-pagina met meerdere bewegende lagen zal een lagere maximum framerate hebben.

Deze test geeft op de volgende systemen en browsers de volgende resultaten:

SysteemNetscape 4.7Mozilla 0.9.8IE 6.0
AMD Athlon 1400MHz, Windows 200050045199,9
Pentium 133MHz, Windows 981824714

Het lijkt er op dat IE het aantal frames per seconde beperkt tot 100fps, waarschijnlijk om het systeem niet te zwaar te belasten.

Het oude 133MHz systeem kan tenminste nog 14 frames per seconde (fps) aan. Voor eenvoudige animaties is dit dus een veilige ondergrens.

Uitgebreid voorbeeld "Mali"

Na zoveel theorie is het tijd voor een echt praktijkvoorbeeld waarin we de verschillende DHTML-technieken toepassen.

Dit voorbeeld werkt dus in alle gangbare browsers, ongeacht het DOM dat wordt gebruikt.

In het voorbeeld verschijnen bovenin de pagina een aantal kleine foto'tjes. Als daarop wordt geklikt, dan schuift een grote variant van deze foto het venster in. We gaan nu steeds een stukje van de broncode bekijken en de werking analyseren. Het is handig om in de browser ook de broncode van de pagina te openen.

Na de bekende html, head en title-tags begint een style sheet. In de eerste helft vinden we stijlen die enkel de vormgeving dienen.

BODY {
	background-color: black;
	margin: 0px;
}
IMG {
	border-color: white;
	margin: 0px 40px;
}
TABLE {
	width: 100%;
}
TD {
	text-align: center;
}
H3 {
	font-family: Verdana, Arial, Helvetica, sans-serif;
	color: white;
	letter-spacing: 4em;
	margin-top: 20px;
}

In de tweede helft van de style sheet worden de stijlen bepaald voor twee layers: foto en selectie. De layer foto bevat de foto in groot formaat en de layer selectie bevat de kleine foto'tjes waarop geklikt kan worden.

#foto {
	position: absolute;
	top: 0px;
	left: 0px;
	width: 100%;
	z-index: 0;
	text-align: center;
}
#selectie {
	position: absolute;
	top: 0px;
	left: 0px;
	width: 100%;
	background-color: darkgray;
	border: 1px solid darkgray;
	border-bottom: 3px solid white;
	padding-bottom: 20px;
	z-index: 1;
	text-align: center;
}

De position: absolute geeft duidelijk aan dat het om een layer gaat die verplaatst kan worden. Verder is aan de z-index te zien dat de selectie-layer hoger ligt dan de foto-layer. De rest dient de formattering en vormgeving.

Netscape 4 toont een achtergrondkleur niet als een vlak, maar alleen achter de tekst. Om toch te zorgen dat de achtergrondkleur een heel vlak beslaat, moet een rand worden toegevoegd: border: 1px solid darkgray;.

Na de style sheet worden de JavaScript-functies gedefiniëerd. De eerste twee hebben we eerder in deze cursus gezien: getStyle() en writeText():

function getStyle( layer ) {
	if( document.getElementById ) {
		return document.getElementById( layer ).style
	} else if( document.all ) {
		return document.all[layer].style
	} else {
		return document[layer]
	}
}
function writeText( layer,text ) {
	if( document.getElementById ) {
		document.getElementById( layer ).innerHTML = text;
	} else if( document.all ) {
		document.all[layer].innerHTML = text;
	} else {
		document[layer].document.open();
		document[layer].document.write( text );
		document[layer].document.close();
	}
}

Daarna volgen twee functies die erg veel lijken op de functies uit voorbeeld 10.

klokje = null
function heen() {
	if ( ypos < 170 ) {
		ypos = ypos + 5
		getStyle('foto').top = ypos
		klokje = setTimeout( 'heen()', 100 )
	}
}
function stop() {
	clearTimeout( klokje )
}

Het enige verschil is dat het hier gaat om een beweging omlaag en dat deze pas stopt als de bovenkant van de foto-layer zich 170 pixels onder de bovenkant van het venster bevindt.

De laatste functie omvat de 'werking' van de pagina:

function laad( foto ) {
	stop()
	writeText( 'foto', "<img src='"+foto+"' width=450 height=675>" )
	ypos = -675
	getStyle('foto').top = ypos
	heen()
}

Steeds als op een van de kleine foto'tje wordt geklikt, wordt deze functie aangeroepen. Als parameter wordt de bestandsnaam meegegeven van de grote foto.

In deze functie wordt als eerste stop() aangeroepen. Die zorgt ervoor dat de beweging wordt gestopt, zoals we in voorbeeld 10 hebben gezien.

Vervolgens wordt met de writeText()-functie een img-tag in de layer geschreven. Hiermee wordt de nieuwe foto in de layer geladen.

Als dat is gedaan wordt in twee regels de vertikale positie op -675 gezet. Omdat de foto 675 pixels hoog is, zal de foto buiten het beeld verdwijnen.

Als laatste in deze functie wordt de eerder gedefiniërde functie heen() aangeroepen.

Als we eindelijk in de head van de pagina terechtkomen, zien we de definitie van de foto-layer:

<div id="foto">
</div>

Dat er niets instaat lijkt een beetje vreemd, maar als we bedenken dat deze layer steeds met writeText() wordt gevuld, wordt alles duidelijk.

Als laatste in de pagina wordt de selectie-layer gemaakt. Dit is het gedeelte dat altijd op de pagina zichtbaar is.

<div id="selectie">
<h3>MALI</h3>
<table><tr>
<td><a href="b1.jpg" onClick="laad('b1.jpg');return false">
			<img src="t1.gif" width=40 height=60></a></td>
<td><a href="b2.jpg" onClick="laad('b2.jpg');return false">
			<img src="t2.gif" width=40 height=60></a></td>
<td><a href="b8.jpg" onClick="laad('b8.jpg');return false">
			<img src="t8.gif" width=40 height=60></a></td>
</tr>
</table>
</div>

De layer begint met een kopje met de tekst 'MALI'. Daarna volgt een table met de drie kleine foto'tjes. De foto's staan in een table omdat anders, om onduidelijke redenen, Netscape 4 de foto'tjes onder elkaar plaatst.

Het enige bijzondere dat nog kan worden opgemerkt is de onClick-attribuut. Hierin wordt de functie laad() aangeroepen met de naam van de te tonen foto. Achter onClick() staat return false, zodat de browser niet zal proberen ook nog de href-link te volgen. In de href staat een gewone hyperlink naar de foto, zodat ook bezoekers die het zonder JavaScript moeten doen, toch de foto's kunnen bekijken.

Uitgebreide voorbeelden "Scroll" en "Invaders"

Hieronder volgen nog twee voorbeelden van DHTML. Net als het "Mali"-voorbeeld werken ook deze voorbeelden in zowel Netscape 4 als de nieuwere browsers. Door de beperkingen van Netscape 4 zijn in deze browser niet alle eigenschappen te zien.

Het eerste voorbeeld laat een tekst in een layer scollen. In dit voorbeeld wordt gebruik gemaakt van de eigenschappen top en clip en van animatie.

Het tweede voorbeeld is een spel, afgeleid van het klassieke Space Invaders. In dit spel worden verschillende DHTML-technieken gebruikt.

Open de broncode van deze voorbeelden en bestudeer de broncode.

W3C DOM

Zoals al eerder geschreven, is de enige, officiële DOM de W3C DOM. Groot nadeel is dat deze alleen nog wordt ondersteund door Internet Explorer 5, Netscape 6 en Mozilla. Dit betekent dat een enigszins groot deel van de internetgebruikers (ongeveer 10%) browsers gebruikt die niet met deze DOM overweg kunnen. Dit is echter een kwestie van jaren. Naarmate de tijd verstrijkt, zullen steeds minder mensen Netscape 4 gebruiken en overstappen op browsers die wel de W3C DOM ondersteunen. Daarnaast ondersteunt de al oude Internet Explorer 4 al een groot deel van de W3C DOM.

Wat biedt de W3C DOM die de oudere DOM's niet bieden? Erg veel! Practisch elk style sheet-kenmerk dat in de Cascading Style Sheets Cursus is behandeld, kan dynamisch worden gewijzigd.

Ook hoeven nu niet meer overal layers voor te worden gemaakt. Elk HTML-element kan een id krijgen en zo is van elk HTML-element de stijl te wijzigen. Een voorbeeld:

<h2 id="kopje">W3C DOM voor Netscape 6+ en Internet Explorer 4+</h2>

De tekstkleur van deze kop kan nu oranje worden gemaakt met:

getStyle('kopje').color='orange'

In plaats van alle mogelijkheden af te gaan, is in het volgende voorbeeld een kopje te vinden, waarvan een aantal stijlen met knoppen zijn te veranderen.

Als we de knoppen goed lezen, dan zien we attributen als borderColor en fontSize in plaats van de bekende border-color en font-size. In JavaScript is het natuurlijk niet mogelijk om een min-teken in de naam van een variabele te hebben. De vertaling van style sheet-kenmerk naar JavaScript-attribuut is heel eenvoudig: de min-tekens worden verwijderd en alle letters die op een minteken volgden worden in hoofdletters geschreven.

Display

Als we een DHTML-pagina maken voor W3C-compatible browsers, kunnen we alle style sheet eigenschappen veranderen. Een interessante is de display-eigenschap. Met display: none kunnen we een deel HTML-code laten verdwijnen en met display: block weer laten verschijnen.

Maar dat kan ook met de visibility-eigenschap. Wat is dan het verschil? Door een tekst met de display-eigenschap te laten verdwijnen, schuift de latere HTML naar boven, alsof een deel van de pagina wordt in- en uitgeklapt.

Dit werkt natuurlijk niet met layers met position: absolute, omdat deze los van de rest van de pagina staan.

Op de volgende pagina staat een voorbeeldformulier, dat door een checkbox aan of uit te vinken, meer of minder velden laat zien.

Omdat deze pagina alleen is bedoelt voor W3C-compatible browsers, is in dit voorbeeld ook de W3C-functie getElementById() gebruikt en niet de cross-browser functie getStyle().

Pixels

In de CSS Cursus konden we lezen dat Netscape en Internet Explorer in de W3C standards-mode gaat als boven in de pagina een DOCTYPE SGML-declaratie declaratie staat.

Als we dat doen bij de DHTML-voorbeelden op deze pagina, is er een grote kans dat ze niet meer werken.

Volgens de W3C-specificaties hoort bij een grootte ook een maat. Dus niet:

getStyle('laag').left = 120;

Maar:

getStyle('laag').left = '120px';

Achter '120' moet dus 'px', 'pt' of een andere maateenheid komen. Dit betekent ook dat het geheel een string (tekenreeks) wordt en we het tussen aanhalingstekens moeten plaatsen.

We zouden de setPosition()-functie van het begin van de cursus weer kunnen nemen en nu bij het W3C gedeelte de string 'px' aan het getal kunnen plakken:

function setPosition( layer, x, y ) {
	if( document.getElementById ) {
		obj = document.getElementById( layer )
		obj.style.left = x+'px';
		obj.style.top = y+'px';
	} else if( document.all ) {
		document.all[layer].style.left = x;
		document.all[layer].style.top = y;
	} else {
		document[layer].left = x;
		document[layer].top = y;
	}
}

Door deze functie te gebruiken, zal een DHTML pagina ook werken in een pagina met een DOCTYPE SGML-declaratie.

Hieronder staat een voorbeeld van een dergelijk pagina.

Events

Events zijn gebeurtenissen. Er zijn heel veel gebeurtenissen, bijvoorbeeld: muiskliks, mouse-overs, on-loads (als de pagina is geladen), keypress enzovoorts. De belangrijkste werden al in Netscape 2 ondersteund. In Netscape 2 tot en met 4 kunnen we helaas niet met elk HTML-element deze events afvangen. In Netscape 4 ziet de browser niet dat de muis over een layer beweegt. De layer kan dus niet reageren op de mouse-over.

In Netscape 6 en Internet Explorer 4 en hoger kunnen wel alle HTML-elementen events afvangen. Er zijn een paar manieren om events af te vangen. Hieronder volgen de twee meest gebruikte.

<h3 onclick="alert('Hallo')">Klik me</h3>

Dit is een H3-kopje met daaraan een onclick-attribuut toegevoegd. Dit kopje reageert dus op muisklikken. Als op het kopje wordt geklikt, dan wordt "alert('Hallo')" uitgevoerd, ofwel een verschijnt een waarschuwingsbox met als tekst 'Hallo'.

De tweede manier is wat omslachtiger, maar uiteindelijk ook wat netter. De JavaScript-code en de HTML-code worden van elkaar gescheiden, wat over het algemeen het beheer van de pagina verbeterd.

In het kort ziet het er zo uit:

document.getElementById('mijnlayer').onclick = mijnfunction

We kunnen dus van elk HTML-element een functie toekennen aan de onclick-eigenschap. Dit kan elke functie zijn, dus ook zelfgeschreven functies. Belangrijk is wel dat achter mijnfunction geen haakjes worden geschreven, anders wordt deze functie eerst uitgevoerd.

Naast onclick kan natuurlijk ook onmouseover worden gebruikt. Het is wel belangrijk om dit met onderkast-letters te schrijven (dus zonder hoofdletters).

Deze functie (mijnfunction) moet vanzelfsprekend wel bestaan. Daarnaast kan de voorbeeldregel hierboven pas worden aangeroepen als mijnlayer al op het scherm staat. Een oplossing is om deze regels pas aan te roepen als de pagina helemaal geladen is. We kunnen daar de body-attribuut onload voor gebruiken.

Overigens zal dit voorbeeld niet werken in Internet Explorer 4 omdat deze browser getElementById() niet kent.

Een pagina met deze vorm van event-afvangen is bijvoorbeeld:

<html>
<head>
<title>Event-afhandeling</title>
<script language="JavaScript">
function pop() {
	alert('u hebt geklikt')
}
function init() {
	document.getElementById('kopje').onclick = pop
}
</script>
</head>

<body onload="init()">
<h3 id=kopje>Dit kopje reageert op muisklikken</h3>
</body>
</html>

In het volgende voorbeeld zien we beide event-afvangers in actie:

Bekijk de broncode voor de werking.

In HTML 4.0 zijn de volgende event-afvangers gedefiniëerd:

onload Treedt op als een pagina wordt geladen. Te gebruiken in <body> en <frameset>
onunload Treedt op als een pagina wordt verlaten. Te gebruiken in <body> en <frameset>
onclick Er wordt op een element geklikt
ondblclick Er wordt op een element gedubbelklikt
onmousedown De muisknop wordt ingedrukt
onmouseup De muisknop wordt losgelaten
onmouseover De muis gaat een element binnen
onmousemove De muis beweegt over een element
onmouseout De muis verlaat een element
onfocus Het element wordt geselecteerd om bijvoorbeeld in te typen. Geldt voor <label>, <input>, <textarea> en <button>
onblur Omgekeerde van onfocus
onkeypress Een toets wordt ingedrukt en losgelaten
onkeydown Een toets wordt ingedrukt
onkeyup en toets wordt ingedrukt en losgelaten
onsubmit Een <form> wordt verstuurd
onreset Een <form> wordt gewist
onselect Tekst wordt geselecteerd. Geldt voor <input> en <textarea>
onchange Een element is gewijzigd. Geldt voor <input>, <select> en <textarea>

Event eigenschappen

Soms willen we een DHTML-pagina maken waarbij we precies willen weten waar de muiscursor staat. Wanneer een event optreedt, kunnen we uit een event object de x en y-positie van de muiscursor lezen.

Het is belangrijk dat we in het HTML-element onClick="toonPositie(event)" het event meegeven. In de aangeroepen functie kunnen we met event.clientX en event.clientY de muispositie uitlezen. Deze positie kunnen we bijvoorbeeld weer gebruiken om een layer te positioneren.

Bestuur ook hier weer de broncode van het voorbeeld.

Er bestaan nog meer event-eigenschappen, bijvoorbeeld om de ingedrukte toetsen uit te kunnen lezen. Omdat deze zelden worden gebruikt wordt hier niet op ingegaan.