Einfache Heatmap mit PHP und Javascript

2 | 12930 Aufrufe
Sie können diese Wikiseite nach der Anmeldung auf Webmasterpro bearbeiten. Helfen Sie mit und verbessern Sie "Einfache Heatmap mit PHP und Javascript" mit Ihrem Wissen!

Anzeige Hier werben

Eine Heatmap ist eine einfache Methode um die am häufigsten aufgerufenen Links zu finden und somit das Interessengebiet der Besucher besser kennen zu lernen. Auch lassen sich Design-/Usabilityschwächen finden, wenn beispielsweise Flächen oft angeklickt werden, die nicht verlinkt sind.

Dieser Artikel soll dabei helfen selbst eine Heatmap für die eigene Webseite zu erstellen. Hierbei werden allerdings nur die Grundlagen angesprochen und ein Script zum Starten angeboten. Komplexere Implementierungen für Datenerfassung und Datenvisualisierung sind natürlich möglich und können bei der Auswertung weiter helfen, sollen aber nicht Teil dieses Artikels sein. Am Ende sind stattdessen einige mögliche Erweiterungen und Ideen für Änderungen gelistet.

Vorüberlegungen

Serverlast

Mit dem hier vorgestellten Script wird bei jedem Klick ein Aufruf eines PHP-Scripts erzeugt. Auch die Datenbank wird damit höchstwahrscheinlich eine nicht zu unterschätzende Größe erreichen, die dank SQLite bei vielen gleichzeitigen Zugriffen erst recht ins Gewicht fallen wird. Der verwendete Server sollte also mit der Mehrlast klarkommen.

URLs

Um aussagekräftige Ergebnisse zu erzielen kann eine Heatmap nur für eine einzelne Seite gültig sein. Deswegen muss nach Adresse gefiltert werden können. Um die Datenbank nicht allzu sehr zu belasten können hierbei Protokoll, Domain, Anker und evtl. sogar der Query-String weggelassen werden. Folgende PHP-Funktion erledigt dies:

hm.php  
PHP
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?php

// Relevanten Teil der Adresse ermitteln
function hm_url($fullUrl)
{
    $url = $fullUrl;
    // Domain und Protokoll wegschneiden
    $url = preg_replace('|^(https?://[^/]*?)?|', '', $url);
    // Optional: Query-String abschneiden
    //$url = preg_replace('|\?.+$|', '', $url);
    // Anker abschneiden
    $url = preg_replace('|#.+$|', '', $url);
    return $url;
}

?>

Natürlich kann auch eine Heatmap, die alle Seiten ausgibt ein aussagekräftiges Resultat erzeugen. Das ist dann möglich, wenn es Elemente gibt die auf jeder Seite gleich positioniert sind. Die Navigation einer Webpräsenz wäre ein Kandidat wo eine solche ungefilterte Heatmap Sinn machen kann. Deswegen wird das Script die Möglichkeit bieten eine solche Heapmap für alle Seiten zu generieren.

Erfassen von Klicks

Da die Klicks im Browser stattfinden und nur hier die genaue Position ermittelt werden kann müssen diese Daten auch hier ausgelesen und an den Server übermittelt werden. Hierzu wird Javascript verwendet:

Klicks erfassen und per AJAX an den Server weitergeben

Mit folgendem Script wird das onmousedown-Event einer Webseite ausgenutzt um einen AJAX-Request bei jedem Klick an den Server zu senden. Die Klickposition wird hierbei direkt per GET an hc.php übergeben, ein Rückgabewert vom Request ist nicht nötig.

Zu beachten sind die Browserunterschiede bzgl. Events, dem Auslesen der Position und natürlich beim AJAX-Objekt. Diese Unterschiede sollen hier nicht näher beschrieben werden, der Quelltext sollte auf Internet Explorer und Firefox funktionieren, andere Browser benötigen evtl. Anpassungen.

hc.js  
JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// Event-Handler für den Klick (onmousedown)
function hm_logClick(e)
{
    // Event-Variable holen falls nötig
    if (!e) e = window.event;
    // Klickposition auslesen
    if (IE)
    {
        // Rechtsklick wird ignoriert
        if (typeof(e.button) != 'undefined' && e.button == 2) return;
        
        clickX = e.clientX + document.body.scrollLeft
        clickY = e.clientY + document.body.scrollTop
    }
    else
    {
        // Rechtsklick wird ignoriert
        if (typeof(e.which) != 'undefined' && e.which == 3) return;
        
        clickX = e.pageX
        clickY = e.pageY
    }
    // Klick-Daten an PHP-Script übergeben
    var url='hc.php?x=' + clickX + '&y=' + clickY;
    hm_sendClickAjaxReq(url);
    // true zurückgeben, damit das Event weiterverarbeitet wird
    return true;
}

// Funktion, die eine URL per AJAX aufruft ohne Rückgabewerte o.ä. zu beachten
function hm_sendClickAjaxReq(url)
{
    // AJAX-Objekt konstruieren
    var ajaxRequest;
    if (typeof(window.ActiveXObject) == 'undefined')
        ajaxRequest = new XMLHttpRequest();
    else
        ajaxRequest = new ActiveXObject('Microsoft.XMLHTTP');
    // AJAX-Request senden
    ajaxRequest.open('GET', url, true);
    ajaxRequest.send(null);
}

// Variable um den IE zu erkennen
var IE = document.all ? true : false
// onmousedown-Event-Handler registrieren
if (!IE) document.captureEvents(Event.MOUSEMOVE)
document.onmousedown = hm_logClick;

Logging der Klicks in einer SQLite-Datenbank

Zum Loggen aller Klicks auf dem Server wird eine SQLite-Datenbank verwendet. Für den produktiven Einsatz sollte evtl. auf eine leistungsfähigere Datenbank gewechselt werden. Für dieses einfache Beispiel einer Heatmap reicht SQLite allerdings völlig aus.

Datenbankaufbau

In der Datenbank muss die Klickposition (x und y) sowie die aufgerufene Adresse (url) gespeichert werden. Zusätzlich wird die Zeit des Aufrufs (time) gesichert, dieses Feld soll eine Anregung für eigene Erweiterungen bieten, wird aber in keinem der hier vorgestellten Quelltexte verwendet (Ausnahme: Beim Eintragen wird das Feld mit der aktuellen Zeit gefüllt).

db.sql  
sql
1
2
3
4
5
6
CREATE TABLE clicks (
    time INT,
    x INT,
    y INT,
    url VARCHAR(255)
);
Logging mit PHP

Die übergebenen Daten können direkt in die Datenbank gesichert werden. Die Adresse der Seite wird hierbei über den Referer übermittelt und mit der oben bereits angesprochenen Funktion vorverarbeitet um überflüssige Daten zu entfernen.

hc.php  
PHP
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?php

// Allgemeine Funktionen laden
require('hm.php');

// Parameter prüfen
if (!isset($_GET['x']) || !isset($_GET['y']) || empty($_SERVER['HTTP_REFERER']))
    exit;
// Klickposition
$clickX = intval($_GET['x']);
$clickY = intval($_GET['y']);
// Positionsdaten validieren
if ($clickX < 0 || $clickY < 0)
    exit;

// Adresse ermitteln
$url = hm_url($_SERVER['HTTP_REFERER']);

// Datenbankdatei
$dbfile = 'clicks.sqlite';
// Testen ob Datenbank existiert
if (file_exists($dbfile))
{
    // Datenbank öffnen
    $db = sqlite_open($dbfile);
}
else
{
    // Datenbank öffnen
    $db = sqlite_open($dbfile);
    // Tabelle anlegen
    sqlite_query(
        file_get_contents('db.sql'),
        $db
    );
}

// Klick loggen
sqlite_query(sprintf('
    INSERT INTO
        clicks
        (time, x, y, url)
    VALUES
        (%d, %d, %d, "%s")',
    time(),
    $_GET['x'],
    $_GET['y'],
    sqlite_escape_string($url)),
    $db
);
// Datenbank schließen
sqlite_close($db);

?>

Ausgabe der Heatmap

Bild zu Einfache Heatmap mit PHP und Javascript
Screenshot der Testseite

Die Heatmap stellt ein einfaches, halbtransparentes Bild dar, welches über die Webseite gelegt wird. Im Bild werden alle Klicks durch rote Kreise dargestellt, sind an einer Stelle mehrere Klicks registriert steigert sich die Intensität des Rot-Tons.

PHP kommt normalerweise mit einer Einschränkung, die beim Erzeugen großer Bilder sehr hinderlich ist: Es kann nur eine begrenze Menge Arbeitsspeicher allokiert werden. Um das zu Umgehen wird die Seite in 250x250 Pixel große Kacheln unterteilt die jeweils durch einen eigenen Request geladen werden. Hierbei wird das Speicherlimit, auch auf streng konfigurierten Servern, nicht erreicht.

Alternativ könnte man mithilfe von init_set() der zur Verfügung stehenden Speicher ändern. Das ist allerdings nicht auf allen Servern möglich. Grundsätzlich ließe sich durch diese Maßnahme allerdings die Last senken, da weniger CPU-Zeit nötig ist. Wer will kann die ideale Größe für die Kacheln auf seinem Server suchen um möglichst nah ans Speicherlimit zu kommen.

Kacheln per JS laden

Um die einzelnen Kacheln zu laden wird per Javascript die Seitengröße ermittelt. Darauf kann die Anzahl der nötigen Kacheln ermittelt werden, diese werden dann als absolut positionierte <div>-Elemente mit der Kachel als Hintergrundbild ans Ende des <body> eingefügt.

hs.js  
JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// Heatmap über die Seite legen, hierbei wird die Heatmap in einem
// 250x250-Raster gekachelt
function hm_loadHeatmap()
{
    // URL ermitteln, wie in hc.php
    var url = location.href;
    
    // Fenstergröße ermitteln
    /* from: http://www.quirksmode.org/viewport/compatibility.html#link2 */
    var width, height;
    var test1 = document.body.scrollHeight;
    var test2 = document.body.offsetHeight
    if (test1 > test2)
    {
        width = document.body.scrollWidth;
        height = document.body.scrollHeight;
    }
    else
    {
        width = document.body.offsetWidth;
        height = document.body.offsetHeight;
    }
    /* --- */
    
    // Body finden
    var body = document.getElementsByTagName('body')[0];
    // Kacheln erzeugen
    for (var x = 0; x < width; x += 250)
    {
        for (var y = 0; y < height; y += 250)
        {
            // Kachel durch ein absolut positioniertes <div> mit
            // Hintergrundgrafik darstellen
            var div = document.createElement('div');
            div.style.backgroundImage = 'url(hi.php?offsetX=' + x + '&offsetY=' + y + '&url=' + url + ')';
            div.style.position = 'absolute';
            div.style.width = '250px';
            div.style.height = '250px';
            div.style.left = x + 'px';
            div.style.top = y + 'px';
            body.appendChild(div);
        }
    }
}
// Optional: Heatmap direkt beim Laden der Seite anzeigen
//window.onload = hm_loadHeatmap;

Kachel mit PHP zeichnen

Um die Kacheln mit PHP zu füllen wird die GL-Lib verwendet. Hierbei wird ein praktisches Feature ausgenutzt: Wird mit einer halb-transparenten Farbe gezeichnet, so wird die Orginalfarbe an der Stelle nicht ersetzt, stattdessen werden die beiden Farbtöne gemischt. Dadurch werden Stellen an denen mehrere Klicks aufgetreten sind im Endergebnis durch einen satteren Rot-Ton dargestellt, die Ballungszentren der Klicks sind also direkt zu erkennen.

Diese Vorgehensweise wird auch eingesetzt um den Klick mit einem radialen Verlauf darzustellen. Um das gewünschte Ergebnis zu erreichen wird einfach mehrfach ein gefüllter Kreis gezeichnet, bei dem der Radius abnimmt. In der Mitte ist somit ein satteres Rot zu sehen.

Damit die Kacheln sauber zusammenpassen werden auch Klicks gezeichnet, die bis zu 20 Pixel außerhalb der eigentlichen Kachel aufgetreten sind. Die GD-Lib zeichnet somit bei einem Kreis mir einem Radius von 7 Pixel noch ins Bild, auch wenn der Mittelpunkt des Kreis leicht außerhalb des Bildes liegt.

hi.php  
PHP
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<?php

// Allgemeine Funktionen laden
require('hm.php');

// Parameter prüfen
if (!isset($_GET['url']) || !isset($_GET['offsetX']) || !isset($_GET['offsetY']))
    exit;
// Variablen initialisieren
$offsetX = intval($_GET['offsetX']);
$offsetY = intval($_GET['offsetY']);
$sizeX = isset($_GET['sizeX']) ? intval($_GET['sizeX']) : 250;
$sizeY = isset($_GET['sizeY']) ? intval($_GET['sizeY']) : 250;
// Variablen validieren
if ($offsetX < 0 || $offsetY < 0 || $sizeX < 0 || $sizeY < 0)
    exit;

// Datenbank öffnen
$dbfile = 'clicks.sqlite';
$db = sqlite_open($dbfile);

// Adresse ermitteln
$url = $_GET['url'];
if ($url != 'all')
    $url = hm_url($url);

// Kachel erstellen
$img = imagecreatetruecolor($sizeX, $sizeY);
imagesavealpha($img, true);

// Hintergrund zeichnen
imagealphablending($img, false);
$back = imagecolorallocatealpha($img, 100, 100, 255, 120);
imagefilledrectangle($img, 0, 0, $sizeX, $sizeY, $back);
imagealphablending($img, true);

// Klicks abrufen
$result = sqlite_query(
    'SELECT
        x,
        y
    FROM
        clicks
    WHERE
        ' . ($url != 'all' ? ('url="' . sqlite_escape_string($url) . '" AND') : '') . '
        x >= ' . intval($offsetX - 20) . ' AND
        y >= ' . intval($offsetY - 20) . ' AND
        x < ' . intval($offsetX + $sizeX + 20) . ' AND
        y < ' . intval($offsetY + $sizeY + 20),
    $db);
$size = 13; // Größe der Punkte
$color = imagecolorallocatealpha($img, 255, 20, 20, 120); // Punktfarbe
// Alle Punkte zeichnen
while ($row = sqlite_fetch_array($result))
{
    for ($i = $size; $i > 0; $i -= 4)
    {
        imagefilledellipse($img, $row['x'] - $offsetX, $row['y'] - $offsetY, $i, $i, $color);
    }
}

// Datenbank schließen
sqlite_close($db);

// Bild ausgeben als PNG
header('Content-type: image/png');
imagepng($img);
imagedestroy($img);

?>

Testseite

Folgende Seite zeigt wie die Scripts eingebunden werden könnten. Natürlich ist das Beispiel nur lauffähig, wenn auf dem Server PHP installiert ist und das Verzeichnis schreibbar ist in dem sich die Dateien befinden. Sonst kann die SQLite-Datenbankdatei nicht angelegt werden.

 
HTML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
<head>
    <title>Testdatei</title>
    <script type="text/javascript" src="hc.js"></script>
    <script type="text/javascript" src="hs.js"></script>
</head>
<body>
    <h1>Nur ein Test</h1>
    <p>Lorem....</p>
    <p>Lorem....</p>
    <p>Lorem....</p>
    <p>Lorem....</p>
    <p><a href="#" onclick="hm_loadHeatmap();">Heatmap zeigen</a></p>
</body>
</html>

Download

Alle hier verwendeten Dateien können für eigene Tests oder Erweiterungen heruntergeladen werden. Die Dateien können von jedem beliebig verändert, weitergegeben oder auf eigenen Seiten eingesetzt werden.

Downloads

Für diesen Artikel stehen zusätzliche Dateien zum Herunterladen bereit. Der Download ist nur für registrierte Benutzer möglich. Die kostenlose Registrierung dauert nur wenige Sekunden.

Mögliche Erweiterungsmöglichkeiten und Ideen

  • Anzeige nach Zeit einschänken können
  • Mehr Details speichern
  • Möglichkeit die Session zu tracken
  • Position der Klicks relativ zu fixen Elternelementen um Abweichung durch unterschiedliche Schriftgröße zu vermindern
  • Browserkompatibilität verbessern
  • Adresse in mehreren Datenbankfeldern speichern um Möglichkeiten bei der Anzeige zu steigern

Wikiseite bearbeiten

Diese Seite kann von jedem registrierten Benutzer bearbeitet werden. Bisher haben 3 Personen an der Seite "Einfache Heatmap mit PHP und Javascript" mitgewirkt.

Sie haben einen Fehler entdeckt oder möchten etwas ergänzen? Dann können Sie nach der Anmeldung "Einfache Heatmap mit PHP und Javascript" hier bearbeiten.

Mitarbeiter