Bernhard Häussner
Tags: Artikel mit dem Tag «Javascript» durchstöbern

jQuery Plugin intoViewport

06.08.2009, 20:08

Das Plugin intoViewport erledigt als Erweiterung zu der populären Javascript-Bibliothek jQuery das scrollen bestimmter HTML-Elemente einer Webseite in den Sichtbereich des Browsers, aber nur wenn nötig und nur so weit wie nötig. Es hat nur eine Größe von 480 Byte (minified).

Demo

Dieses minimale jQuery-Beispiel würde den 4. Link auf der Seite in das Darstellungsfeld des Betrachters scollen:

$('a').eq(4).intoViewport();

Scrollen - so viel wie nötig und so wenig wie möglich

Das Script vergleicht jeweils Höhe und Y-Position des übergebenen DOM-Elements und des aktuell sichtbaren Seitenausschnitt des Browsers (Viewport).

Sollte das Element über den Viewport herausragen, oder gar komplett über diesem sein, scrollt es so weit nach oben, bis das gesamte Element sichtbar ist, also wird die Oberkante des Ausschnitts an die Oberkante des Elements verschoben.

Sollte das Element hingen weiter unten liegen, scrollt es nach unten, und wiederum nur so weit, dass es Element gerade sichtbar ist.

Ist das Element schon komplett im Viewport, so tut es überhaupt nichts. Die Scroll-Maßnahmen sind möglichst restriktiv gehalten, da scrollen ungefragt, oder gar sinnlos, den Benutzer der Seite für gewöhnlich verwirrt.

Im Fall, dass das Element größer als das Browserfenster ist, versucht das Plugin möglichst viel des Elements anzuzeigen und scrollt die Oberkante des Elements an den Oberen Bildschirmrand, sodass man immer noch oben beginnen kann zu Lesen.

Wieso, Weshalb, Warum?

Das Plugin soll verhindern, dass, beispielsweise mit jQuery, später eingeblendete Elemente vom Surfer unbemerkt erscheinen, wie Warnmeldungen.

Benötigt habe ich das Plugin auch für meine Suchfunktion, wo nach Möglichkeit die Ergebnisliste komplett angezeigt werden soll. Denn hier sind versteckte Teile fatal: man tippt (meist) mit beiden Händen und kann daher nicht scrollen. Außerdem wird bei der Tastaturnavigation durch die Suchergebnisse der Bildschirmausschnitt dem gewählten Ergebnis folgen.

Die Einsatzgebiete sind sicher noch vielfältiger und der Kreativität sind, wie immer, keine Grenzen gesetzt.

Es grenzt sich von der Javascript-Funktion scrollIntoView() dadurch ab, dass es sanft (animiert) scrollen kann, dadurch den User nicht total orientierungslos lässt. Zwar gab es schon Lösungen zum sanften Scrollen mit jQuery, aber diese beinhalteten leider nicht den Test, ob und wie weit gescrollt werden muss.

Der Javascript-Quelltext

Für Interessierte, hier der Sourcecode des Plugins:

(function($) {
  jQuery.fn.intoViewport = function(options) {
    options = $.extend({
      // Configuration
      // Add whatever options animate schould get by default
      duration:  200,
      easing: "swing"
    }, options || {});
    return this.each(function(){
      // scroll to certain destination:
      function scrTo(dest) {
        $("html,body").stop().animate({ scrollTop: dest}, options );
      }
      var
        //current viewport Y-position
        scr=$(document).scrollTop()||$(window).scrollTop(),
        // viewport Y-size
        wheight=$(window).height(),
        // element Y-position
        top=$(this).offset().top,
        // element Y-size
        eheight=$(this).outerHeight();
        // case element before viewport:
        if (scr>top) {
          scrTo(top); // scroll up to element
        // case viewport before element (bottom part of e. outside):
        } else if (scr!=top && top+eheight>scr+wheight) { 
          // scroll down till everything is inside
          scrTo(top+Math.min(eheight-wheight,0));
          //              ^ but don't hide top part again
        }
      });
    };
})(jQuery); //compatibility

Ich habe das Plugin allerdings nicht nicht in zu vielen Browsern und Situationen getestet. Daher könnte es sein, dass es in einigen Browsern nicht funktioniert. Auch könnte es Probleme geben, wenn die Elemente innerhalb von scrollbaren Elementen liegen, wie <div>s mit der CSS-Eigenschaft overflow:auto;.

Kommentare: 2 Einträge

Javascript Library Builder

31.07.2009, 13:07
JS-Build

JS-Build

Es gibt einige Methoden Javascript auf HTML-Seiten einzubinden. Ein Block im <body>, in onclick-Attributen und ähnlichem, oder als Verknüpfungen im <head> aber am besten in einer externen Datei, die nach dem HTML-Dokument geladen wird. Nur will man beim Entwickeln die einzelnen Javascript-Teile meist in getrennte Dateien legen und auf der Seite dann möglichst viel in eine (am besten möglichst kleine) Datei packen, um Requests zu sparen. Dieses Problem kann der Javascript Library Builder lösen, ein recht kurzes Shell-Script, welches eine Sammlung von .js-Dateien in wenige Dateien minimiert.

Man nehme zum Beispiel eine solche Dateistruktur:

lib/
  php/...
  js_frontend/
    00_jQuery.js
    01_jQ_plugin.js
    02_effekte.js
    licence.txt
  js_backend/
    03_ajaxCalls.js

Der Scriptaufruf wäre so einfach wie:

jslibbuild.sh -o build lib/js* 

Ich habe auf dieser Seite 27 einzelne Dateien, die ich in zwei Javascript Dateien packe, nämlich für die Seite und für das CMS. Diese Beispiel-Dateistruktur würde jetzt in den zwei Dateien js_frontend.min.js und js_backend.min.js im Ordner build resultieren. Der Datei js_frontend.min.js würde zusätzlich nach dem minimieren mit jsmin die Datei licence.txt vorgehängt, sodass z.B. in Kommentaren (die von jsmin geschluckt werden) vermerkte Autorenhinweise erhalten bleiben, was unter anderem bei den CC-Lizenzen nötig ist.

Dazu akzeptiert das Script die optionale Option -l nach der der Name der Lizenz-Dateien angegeben wird. Eine weitere optionale Option ist -c, um das build-Verzeichnis vor dem Anlegen der neuen Dateien zu löschen. Das ist nötig, wenn ein ganzer source-Ordner gelöscht wird, aber dringend zu vermeiden, wenn weitere Dateien im build-Ordner liegen. Hier noch ein Beispiel:

jslibbuild.sh -c -l gpl.txt -o /srv/www/htdocs/js ~/web/lib/js/* 

Das Script benötigt jsmin, steht unter GPLv2+ und kann hier heruntergeladen werden:

Zum „installieren“ einfach in den Ordner ~/bin kopieren. Das Script ist eigentlich ziemlich einfach, kann aber doch einiges an Arbeit ersparen.

Kommentare: keine

Twitter Bookmarklet III

11.07.2009, 20:16

For twitter users: Here's an easy-to-use toolbar button for your browser that substitutes the URL shortening and copy-pasting for you. There's no installation, I don't want to know your twitter login or password, and it will shorten the URL using bit.ly.

The bookmarklet will:

  • Receive a short URL from the bit.ly API
  • Open up your twitter homepage in a new tab (Firefox) or in a pop up with the short URL and the page title included in the textarea
  • (Or take you to the twitter login page, which will then redirect)

To install just drag this link/button into your browser's bookmarks toolbar:

tweet

The bookmarklet is even supporting flickr-Photos (using their short URL like http://flic.kr/p/6qxGR2) and other web sites (Ars technica, PHP-Manual, this blog...) if they provide own shortening services.

The code is a bit inspired by John Resig's „retweet“ button but it's not on the page you are viewing, but in your browser's toolbar, so you don't rely on webmasters cluttering their designs with social bookmarking services' buttons.

For IE-Users: You will have to right-click on the link and choose save bookmark. After that, you will be warned, because it's not a simple link, but a bookmarklet.

Kommentare: 2 Einträge

MacMahonMosaik Online Game

09.07.2009, 18:05
Mac Mohan Mosaik

Mac Mohan Mosaik

Da ich beim Känguru-Wettbewerb 2009 mitgemacht habe, habe ich auch das MacMahonMosaik in die Hände bekommen. Es ist ein kleines Rätsel ähnlich wie Sudoku. Weil es interessant zu Modellieren ist, habe ich das MacMahonMosaik als Online-Spiel erstellt. Hier ein paar Details zu Implementierung:

Modell

Jedes Quadrat im Spiel hat 4 Kanten, die in 3 verschiedenen Farben markiert sein können. Daher lässt sich jede Anordnung als 4-Tupel einer 3-Menge auffassen. Diese 81 Tupel wiederum können den natürlichen Zahlen von 0-80 zugeordnet werden. Somit habe ich als Objekte zunächst die 24 Quadrate (die anderen fallen weg, da sie durch Drehung erreicht werden können), die jeweils eine Zahl speichern. Sie müssen sich nun z.B. drehen können. Dazu wird die Zahl in das Tupel umgerechnet und die Einträge des Tupels um eine Stelle verschoben, wobei der letzte Eintrag der erste wird. Um zu Überprüfen, ob zwei Quadrate aneinander passen, dreht man eines der beiden zweimal und schaut dann einfach ob in der gewünschten Richtung die selben Farben stehen. Dadurch sieht die Prüffunktion im Javascript sehr kurz aus:

p.checkMatch=function(other,pos){
  var mycodes=this.getColorCodes(); // Tupel aus Zahl
  var o=other.clone(); // Neues Quadrat mit selber Zahl
  var othercodes=o.rotate().rotate().getColorCodes(); // anderes Q. 2x drehen
  return (mycodes.charAt(pos)==othercodes.charAt(pos)); //prüfen
}

Zusätzlich haben die Quadrate noch ein paar Darstellungsrelevante optionale Eigenschaften und Methoden. Ist ihnen ein <Canvas>-Element im HTML-Baum delegiert, können sie ihm die passenden Event-Handler für das interaktive Drehen und Bewegen zuweisen und eine graphische Repräsentation ihrer selbst zeichnen. Und sie speichern einige Daten für drag & drop.

Doch beim Ziehen und Ablegen kommt das zweite Objekt ins Spiel: Das Feld in dem die Quadrate abgelegt werden können. Wann immer man ein Quadrat bewegt, frag dieses Quadrat beim Gitter nach, ob es sich einrasten kann. Das Gitter gäbe dann die Pixel-Koordinaten des Einrastpunkts auf dem Bildschirm und die Koordinaten innerhalb des Gitters zurück. Wird das Quadrat dann fallen gelassen, registriert es sich im Gitter.

Das Gitter hat dazu ein zweidimensionales Feld hinterlegt, in dem es Pointer zu den abgelegten Quadraten speichern kann. Wenn jetzt ein Quadrat im Raster abgelegt wird und sich registriert hat, weist es das Raster an, sich zu revalidieren - Das Gitter ruft für jedes Quadrat und je seine vier Nachbarn die Prüffunktion auf. (Da das „Passen“ symmetrisch ist, ließe sich die Komplexität des Algorithmus vielleicht noch optimieren, doch das hätte wieder mehr Code zur Folge und bei 24 Einheiten...) Da das Gitter nur Zeiger zu den Quadraten speichert, ist bei einer Drehung eines Quadrats keine weitere Registrierung nötig. Falls also ein Quadrat angeklickt wird, rotiert es sich, aktualisiert die eigene Darstellung und gibt den Revalidierungsbefehl an das Raster.

Für die Darstellung hat das Gitter auch eine recht einfache Methode, die den Feldhintergrund rötlich färbt, sollte ein Fehler vorliegen und eine Gratulation meldet, sollten alle Quadrate an einem passenden Ort sein, also das Spiel gewonnen.

Ablaufdiagramm

Hier mal ein kleiner Flowchart. Man beginne das Lesen bei den Event-Handlern: (erstellt mit U+2500-U+257F: Box Drawing)

Quadrat
├ Zahl ◁──────┐ {Umrechnung}
├ (Tupel) ◁───┘
├ Drehen() ◀────────┐◁──┐─┐▷─────┐
├ Prüfen(Quadrat) ──◆───┊─┊──────┊──┐
├ [Darstellung]     │   │ │      │  │
│  ├ Visualisieren◁─┊───┊─┘      │  │
│  └ [Handler]      │   │        │  └──┐
│     ├ Klick ──────┊───┘        │     │
│     ├ Packen  ────┊────────────┊──┐  │
│     ├ Bewegen ◀───┊───┐        │  │  │
│     └ Ablegen ────┊─────────┬▷─┤  │  │
└ Kopieren() ►──────┘   │     │  │  │  ▼ bool
                     [XY,XY]  │  │  │  │
Gitter                  │     │  │  │  │
├ Feld[X,Y]             │     │  │  │  │
├ KoordinatenBei(XY) ───┘     │  │  │  │
├ Registrieren(Quadrat)  ◀─XY─┘  │  │  │
│ └ Un-Registrieren() ◀─────XY───┊──┘  │
├ Alle Prüfen()  ►────────┐─▷────│─────┘
└ Darstellung erneuern() ─┘  ◁───┘
  └ Färben(), Gratulation()

Wie das Gitter überprüft, ob auch am Rand rote Kanten liegen? Statt weiteren Code für den Rand zu basteln, wird das Gitter mit voll-roten Quadraten ohne graphische Repräsentation außerhalb das Gitters vorbelegt. Das hat den kleinen Nebeneffekt, das auch out-of-bounds-Fehler im Prüfalgorithmus vermieden werden, da ja z.B. bei negativen Gitterkoordinaten -1 stets ein solches „virtuelles“ Quadrat liegt.

Wie man also sieht, kann man an diesem Spiel schon einige lustige Code-Spar-Methoden benutzen. Eigentlich sparen sie nicht nur Code, sondern auch Nachdenken, wenn man sie „sieht“.

Javascript Umsetzung

JS-Closures habe ich in letzter Zeit immer mehr zu schätzen gelernt. (Man muss in PHP übrigens auch nicht mehr lange darauf verzichten) Sie lassen den Programmierer Funktionen, also mehr oder weniger Code, wie ein String oder eine Zahl an andere Funktionen übergeben oder in Variablen spichern, sodass mit anonymen Funktionsobjekten als Closures in JS eigene Kontrollstrukturen definiert werden können, wie diese zweifache for-Schleife, um über Koordinaten bzw. Zweidimensionales zu loopen:

function loopXY(w,h,cb) {
  for (var x=0;x<w;x++) {
    for (var y=0;y<h;y++) {
      cb(x,y);
    }
  }
}
var m=[];
loopXY(5,7,function(x,y){
  m.push("("+x+","+y+")");
});
alert(m);

Wie man sieht stehen in Javascript in den Closures alle äußeren Variablen zur Verfügung. Das wiederholte verwenden des Namens einer äußeren Variable führt trozdem noch dazu, dass in der Closure diese lokal verwendet wird und außen der alte Wert erhalten bleibt. Ein bischen gewöhnungsbedürftig ist die Verwendung von this in JS. Im oben gezeigten Code würde z.B. in der Schleife this auf window zeigen. Das lässt sich mit der Function.call oder der Function.apply-Funktion kompensieren, wie in diesen Code-Schnippseln für Event-Handler.

Für die Erstellung von Objekten (außer reinen Datenspeichern, die mit {} einfach erstellt werden können) verwende ich folgende Grundstruktur:

function Square(id) { // constructor
	this.id=id; // Eigenschaften initialisieren
}
(function(){
  var p=Square.prototype; // shortcut zum Prototype
  p.setId=function(id) { // einfacher Setter
    this.id=id;
    return this; // macht Setter chainable
  };
  var privat; // kann nur von den Objektmethoden gesehen werden
  function privatauch() {} // dito
})();
var s=new Square (3);

Allerdings bin ich mir nicht sicher, ob das wirklich besser ist, als alles in Konstruktoren zu packen.

Der Code des Onlinegames ist vielleicht nicht ganz so interessant zu lesen, wie es war ihn zu erstellen, aber ich denke, hinein schauen lohnt sich. Ich bin natürlich immer interessiert an weiteren Javascript-Techniken und Tipps zur Code-Gestaltung.

Kommentare: keine

Ein paar Code-Schnipsel

15.06.2009, 19:30
Javascript

Javascript

In diesem Post nur ein paar kleine Code-Auszüge und Techniken für Webanwendungen. Inhalt:

  • Data URI scheme für Favicons & CSS-Farbverläufe
  • HTML-Attribut contenteditable zum einfachen Editieren
  • Javascript: Globale Variablen einfach vermeiden
  • JSON in Script-Tags
  • JSON-Parser bei Bedarf laden
  • jQuery nach hinten verlegen
  • jQuery mouserest-Plugin

Data URI scheme für Favicons & CSS-Farbverläufe

Eine mir bisher unbekannte Funkion moderner Browser (ab IE8) sind data: URLs. Anders als normale http:// oder file:// URLs repräsentieren sie keinen Link, sondern die Daten sind komplett in der URL enthalten. Damit man auch Binärdaten verwenden kann (oder sich das escapen von HTML-Tags, „"“ etc. sparen), ist es möglich optional mit Base64 zu encodieren. Für ein Favicon sieht das dann so aus:

<link rel="shortcut icon" href="data:image/png;base64,iVBORw[...]RK5CYII="/>

Und für eine Graphik im CSS:

body {background:url(data:image/png;base64,iVBORw[...]ElFTkSuQmCC);}

Das lohnt sich bei kleinen Farbverläufen oder Symbolen, da ein zusätzliches Request zum Server mehr Bytes brauchen würde, als die Base64-Codierung produziert. Da bei base64 jeweils 3 Bytes zu 4 werden, sieht die Rechnung für die maximale PNG-Größe so aus:

PNG*(4/3) + 55 B = HTTP-HADER
mit HTTP-HADER:=600B PNG=1635 B

Da man im CSS mittels Sprites sowieso Requests sparen kann und durch Kombination größere PNGs bekommt, lohnt es sich hier also nur bei kleinen Projekten. (Oder bei riesigen Cookies, was die Request-Größe in die Höhe treibt. Da würde ich dann aber eine static-Subdomaine empfehlen. )

Als weiteren Vorteil kann man eine ganze Seite in eine HTML-Datei packen, jedoch kann man auch die Bilder nicht mehr so leicht bearbeiten.

HTML-Attribut contenteditable zum einfachen Editieren

Auch ein spaßiges Feature sind editierbare HTML-Elemente. Man kann das HTML-Attribut contenteditable für einzelne HTML-Elemente setzten oder gleich für die ganze Seite:

<body contenteditable="true">

Das Attribut ist zwar erst mit HTML5 valid, wird aber schon von einigen älteren Browsern unterstützt.

Javascript: Globale Variablen einfach vermeiden

Mit wachsender Zahl an Javascript-Bibliotheken wird die Wahrscheinlichkeit der Kollision globaler Variablen immer größer. Zwar ist es unwahrscheinlich, dass man z.B. $ überschreibt, aber manche Javascript Frameworks haben sich ja sogar zueignen gemacht, die Prototypen der Sprachfunktionen zu erweitern, was natürlich beim Einsatz eigener/anderer Prototypen-Erweiterungen nicht-überschaubare Probleme erzeugen kann. Das lässt sich aber vermeiden, indem man eine Art jQuery für andere Aufgaben erzeugt. Dazu kann man z.B. die ersten ~50 Zeilen von jQuery grob kopieren1, einen eigenen Framework-Namen einsetzen und dann eigene Funkionen einbauen, was man dann z.B. so verwenden kann:

var foo=Array(1,2,3,4);
alert(myFW(foo).contains(4));

Hier benutzt man dass die Funktion myFW einen Weiteren Konstruktor aus sich selbst aufrufen kann.

Es gilt zusätzlich globale Variablen zu vermeiden. Doch man kann für einzelne Komponenten Kürzel als Arrays verwenden, die dann die Globalen speichern. So kann man auch im DOM-Explorer von Firebug einen Überblick über globale Variablen behalten und die Komponenten können kommunizieren.

1 Ich vermute, wer den ganzen jQuery-Code aufmerksam gelesen hat, kann mehr als man in allen Javascript Büchern zusammen lernt.

Um den Speicherbedarf nicht unnötig in die Höhe zu treiben kann man die Variablen-Sichtbarkeit immer recht klein halten, damit die GC die Daten wieder fressen kann, wenn sie nicht mehr gebraucht werden. In JS sind übrigens auch Funktionen nicht global und löschbar. Funktionen, die nur beim Initialisieren gebraucht werden, kann man also mit delete funktionsName; wieder aus dem Speicher nehmen.

Ähnlich der main()-Funktion in anderen Programmiersprachen kann man in Javascript den Code zusätzlich in eine gleich aufgerufene, anonyme Funktion hüllen:

(function(){
  var nichtglobal;
  // code
})();

So kann man ganz normal „globale“ Variablen und Funktionen verwenden, aber ein anderes Script bemerkt davon nichts.

JSON in Script-Tags

Wenn man für Javascript Daten übertragen will, dann bietet sich JSON an, da es einfach Daten austauschen kann und in vielen Programmiersprachen implementiert ist. Da gibt es die eine Methode, JSONP, welche die Daten in einer Variable speichert, bzw. an ein Callback übergibt. Alternativ kann man auch die Daten in <script type="text/x-json" id="dafaultData">-Tags im DOM platzieren und dann mit Javascript auslesen und parsen

JSON-Parser bei Bedarf laden

Da moderne Browser jetzt native JSON-Parser bekommen, muss man den JSON-Parser nicht immer laden. Das könnte man so lösen:

<script>
  if(!this.JSON||typeof JSON.stringify !== 'function'||typeof JSON.parse !== 'function'){
    document.write("<scr"+"ipt type=\"text/javascript\" src=\"json2.js\"></scr"+"ipt>");
  }
</script>

jQuery nach hinten verlegen

Ganz praktisch hat es sich erwiesen, das Javascript nach dem meisten Markup zu laden. So wird durch CSS und HTML schonmal die Seite aufgebaut und dann erst werden die Javascript-Verbesserungen geladen.

Viele Webanwendungen, z.B. Google Mail, zeigen zunächst einen Ladebildschirm. Dadurch fühlt sich die Anwendung gleich viel langsamer an. Schneller wird die Anwendung wenn zunächst der grobe Aufbau steht, mit HTML und CSS zuerst geladen, dann ein kleines Skript erste Programmteile implementiert, sodass die Optik fertig erscheint. Dann läd man jQuery und dann erst weitere Javascripts (die auf jQuery basieren). So kann sich die Seite blitzschnell aufbauen, ohne dass erst jQuery geladen werden muss.

jQuery mouserest-Plugin

Nicht wirklich ein „Plugin“ aber eine kleine Hilfsfunktion. Sie führt ein Callback aus, nachdem die Maus einige Zeit auf dem Element war:

jQuery(function(){ // plugins
  jQuery.fn.mouserest=function (callback,time) {
    jQuery(this).mouseenter(function(e){
      this.dsthTo=setTimeout(function (obj){callback.apply(obj)},time,this);
    }).mouseleave(function(){
      clearTimeout(this.dsthTo);
    });
  }
});

Das ist ganz praktisch, denn wenn man die Zeit (in ms) auf 200 setzt, lässt sich verhindern, dass bestimmte Effekte beim bloßen Überfahren auftreten. Sondern der Event wird erst ausgelößt, wenn die Maus eine kurze Zeit auf dem Element war. Im callback kann man übrigens durch das apply() mit this auf das DOM-Element zugreifen, wie es in jQuery üblich ist.

Kommentare: 2 Einträge
« Vorherige Seite [ Seite 1 2 ]
 
Χρόνογραφ
© 2008-2017 by Bernhard Häussner - Impressum - Login
Kurz-Link zur Homepage: http://1.co.de/