kritikant

laut nachgedacht

Zum Zubehör springen

Kategorie: Webdesign

Vererbung von viewport-percentage lengths in Chrome

Ich stolperte neulich über ein Problem mit viewport-relativen Längen in CSS. Die Situation war konkret folgende:

Ich hatte eine ungeordnete Liste, deren Elemente je ein <div> enthielten, in dem sich wiederum ein <a> befand:

<ul>
    <li>
        <div><a href="#">Test</a></div>
    </li>
</ul>

Zuerst ein Beispiel, wie es funktionieren sollte. Die <li>-Elemente sollen eine definierte Höhe haben und die <div>s sollten genauso hoch sein. Das CSS dazu:

* { margin:0; padding:0; }
ul { list-style-type:none; }

li {
    background:blue;
    height:10em; 
}
div {
    background:red;
    height:100%; /* Genau so hoch wie sein Container */
}

Hier ist ein JS-Fiddle dazu. Wie erwartet bedeckt das rote <div> das gesamte Listenelement. Ruhig auch mal in verschiedenen Browsern ansehen.

Jetzt geben wir dem <li>-Element aber eine vom Viewport abhängige Größe:

li {
    height:30vw; /* Die Höhe soll 30 Prozent der Breite des 
                    Viewports betragen */
}

Hier die geänderte Demo.

Im Firefox sieht es immer noch genau so aus wie vorher. Das würde man (ich) auch erwarten. Schließlich hat das Listenelement eine definierte Größe und ich sage, dass sein direktes Kindelement 100% dieser Größe haben soll. Sieht man sich das Ergebnis in Chrome an, zeigt sich aber ein anderes Bild.

Das <li> hat zwar die richtige Größe, aber das <div> ist nur so hoch, wie es sein Inhalt (eine Textzeile) erfordert. Sogar im IE9 wird es korrekt (wie im FF) dargestellt. Opera unterstützt derzeit keine viewport-related lengths, aber da auch dort demnächst ein Webkit rendert, wird es sich wohl auch nur mittelmäßig zum Guten ändern.

Anscheinend muss man derzeit also entweder für Chrome in solchen Fällen auch jedem Kindelement die v*-Größe zuweisen, oder mit anderen Längeneinheiten arbeiten. Schade.

jQuerys scrollTop() und border-box

Das Folgende betrifft soweit ich sehe nur jQuery < 1.8.

Letzte Woche habe ich den ersten Kandidaten für den irrsten Bug in diesem Jahr gefunden.

Der Fehler, den der Kunde berichtete, beruhte darauf, dass das ursprünglich verwendete $(document).scrollTop() nicht im IE8 funktionierte und nachdem er dies durch das funktionierende $('html').scrollTop() ersetzt hatte, lief es nicht mehr in Webkit-Browsern.

Die Lösung dafür war relativ schnell gefunden: Ich ersetzte einfach in dem betreffenden Script den einen scrollTop()-Aufruf durch den Ausdruck ($(document).scrollTop() || $('html').scrollTop()). Damit war der Drops gelutscht und jeder Browser konnte sich aussuchen, was ihm passte (bzw. was nicht gleich 0 war, da der Code nur beim Scrollen zur Anwendung kam, reichte das aus).

Aber was war die Ursache? Nach viel Ausprobieren blieb mir nichts übrig als in die jQuery-Source zu schauen und mir anzusehen, wie jQuery.scrollTop() definiert ist. Für das verwendete jQuery 1.6.4 sieht das so aus:

// Create scrollLeft and scrollTop methods
jQuery.each( ["Left", "Top"], function( i, name ) {
	var method = "scroll" + name;

	jQuery.fn[ method ] = function( val ) {
		var elem, win;

		if ( val === undefined ) {
			elem = this[ 0 ];

			if ( !elem ) {
				return null;
			}

			win = getWindow( elem );

			// Return the scroll offset
			return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] :
			jQuery.support.boxModel && win.document.documentElement[ method ] ||
			win.document.body[ method ] :
			elem[ method ];
		}

		// Set the scroll offset

		// Interessiert uns hier nicht
	};
});

Der Fall des IE8 sollte mit der Zeile abgedeckt werden, die mit jQuery.support.boxModel beginnt. Diese Eigenschaft dient eigentlich dazu, zu überprüfen, ob das W3C-Box-Modell vom Browser unterstützt wird. Das Problem ist, in diesem Fall gibt sie false zurück, obwohl das Dokument in Ordnung ist und der Browser sich im Standardmodus befindet. Warum? Also nachsehen, wie jQuery.support.boxModel gesetzt wird:

// Figure out if the W3C box model works as expected
div.style.width = div.style.paddingLeft = "1px";
support.boxModel = div.offsetWidth === 2;
Es wird ein Test-Div erzeugt, dem verschiedene Eigenschaften zugewiesen und das dann an den body angehängt wird. Da das Element hier einen Pixel breit ist und außerdem ein Padding von einem Pixel erhält, sollte offsetWidth, das die Gesamtbreite enthält, 2px zurückgeben.

Tut es aber nicht.

Der Grund dafür ist, dass ich im CSS box-sizing: border-box; gesetzt habe. Das ist eigentlich eine prima Sache, denn dadurch sind Elemente, denen man eine bestimmte Breite zuweist, auch wirklich so breit – egal ob sie Innenabstände oder Rahmen enthalten. Aber in diesem Fall passiert dann folgendes: Wir geben einem Element den linken Innenabstand 1px und sagen dann, dass das gesamte Element, mit Innenabständen und Rahmen, 1px breit sein soll. Das bedeutet, für den eigentlichen Inhalt ist kein Platz mehr, was egal ist, weil das Element sowieso keinen Inhalt hat und nur zum Testen da ist. Aber das bedeutet auch, dass offsetWidth nun auch 1 enthält, das ist schließlich die Gesamtbreite des Elements. Damit schlägt der Test div.offsetWidth === 2 natürlich fehl, und scrollTop() gibt nicht mehr window.document.documentElement.scrollTop zurück, sondern 0.

Darauf muss man erstmal kommen

Ab jQuery 1.8 besteht das Problem nicht mehr, weil dann nicht mehr auf Box-Model-Support getestet wird bzw. dieser Test nicht mehr in scrollTop() abgerufen wird.

Warum Webkit übrigens $('html').scrollTop() nicht versteht, ist mir noch nicht so ganz klar.

.htaccess-Spielereien

Mit .htaccess-Dateien kann man den Zugang zu den einzelnen Dateien oder Dokumenten auf seinem (Apache-)Webserver sehr bequem regeln. Es lassen sich zum Beispiel einzelne Verzeichnisse nur für Nutzer mit einer bestimmten IP freigeben. Oder man kann Passwörter für den Zugang vergeben. Vor ein paar Tagen habe ich herausgefunden, dass man mit ihnen auch wunderbar beliebige andere Servervariablen abfragen kann.

Mit dem Modul mod_setenvif nämlich lassen sich mit Hilfe der zwei Direktiven BrowserMatch und SetEnvIf (und deren Varianten, denen Groß- und Kleinschreibung egal ist) Umgebungsvariablen abfragen und davon abhängig den Zugang zu Ressourcen regeln.

Ich habe das benutzt, um folgendes zu tun. Ein PHP-Dokument bindet mit require eine HTML-Datei ein und zwar abhängig vom Rückgabewert eines If-Statements:

if (…) { require "eingebunden.html";}

Auch wenn ich den Namen der HTML-Datei so wähle, dass er schwer erraten werden kann, möchte ich sichergehen, dass niemand einfach die Adresse der Datei in den Browser eingeben und so ihren Inhalt anzeigen kann. Das geht jetzt ziemlich einfach, indem ich eine .htaccess-Datei in das Verzeichnis lege, in dem sich die fragliche HTML-Datei befindet und sie mit diesem Inhalt versehe:

SetEnvIf Request_URI "eingebunden\.html$" verboten
Deny from env=verboten

Mit der SetEnvIf-Direktive setze ich eine Variable namens „verboten“ genau dann wenn der URI-String, den der Browser als Request gesendet hat, mit dem regulären Ausdruck "eingebunden\.html$" übereinstimmt. Als nächstes sage ich dem Server, dass er Anfragen, für die er diese Variable erzeugt hat, ablehnen soll. Versucht man jetzt die HTML-Datei direkt im Browser aufzurufen, ist das einzige, was man sieht, ein 403.

Flattr this