Es ist für jeden, der diese Seite besucht, wahrscheinlich offensichtlich: Treehouse ist unser Hauptsponsor. Das gefällt mir. Nicht nur, weil sie strukturiertes Lernen anbieten, das die Lernmaterialien ergänzt, die ich hier habe, sondern auch, weil ein einziger Hauptsponsor bedeutet, dass wir mehr Zeit für die Integration von Anzeigen auf unterhaltsame Weise haben.
Im Gespräch mit den Leuten von Treehouse erfuhren sie, dass sie neue jQuery-basierte Inhalte hatten, die sie bewerben wollten. Ich dachte mir, ich würde einen kleinen interaktiven Bereich erstellen, um den Leuten Appetit auf das zu machen, was jQuery leisten kann, und sie neugierig auf mehr zu machen.
Gehen wir durch, wie es gebaut wurde.
Die Idee
- Ein „Leinwand“-Bereich (nicht
<canvas>, sondern einfach ein Bereich, in dem Dinge passieren) - Eine Reihe von fünf einfachen jQuery-Code-Schnipseln
- Klicke auf einen Button, Code wird ausgeführt, etwas passiert auf der Leinwand
- Du kannst den Code sehen, den du gleich ausführen wirst
- Es sollte visuell klar sein, dass der angezeigte Code derjenige ist, der ausgeführt wird und die Änderungen vornimmt
- Die gesamte Leinwand ist währenddessen eine klickbare Anzeige für Treehouse (Nutzer müssen nicht mit den jQuery-Sachen interagieren, es ist immer noch nur eine Anzeige)
- Du kannst die interaktiven Schritte nach Abschluss neu starten

Hier ist das Endergebnis. Demo auf CodePen
Das HTML
Relevante Stellen kommentiert
<div class="ad">
<!-- Our jQuery code will run on elements in here -->
<div class="canvas" id="canvas">
<a id="treehouse-ad" href="http://teamtreehouse.com" target="_blank">Team Treehouse!</a>
<img src="//css-tricks.de/wp-content/themes/CSS-Tricks-10/images/mike-standard.png" alt="Mike">
</div>
<div id="code-area-wrap">
<h3>jQuery
<!-- We'll increment the "step", so we need an element to target -->
<span id="step-counter">1/5</span>
</h3>
<!-- Code is visually displayed in here -->
<pre class="code-area" id="code-area">var treehouse =
$('#treehouse-ad')
.addClass('button');</pre>
</div>
<div class="run-code-wrap">
<!-- This will be pressed to run the code -->
<a href="#" class="button" id="run-code">Run Code</a>
</div>
</div>
Das CSS
Nicht viel Interessantes hier, also lass uns nicht zu lange dabei verweilen. Im Grunde habe ich die Anzeige so gestaltet, dass sie bereits verwendete Stile von CSS-Tricks wiederverwendet, damit die Anzeige sich wie zu Hause fühlt. Der Codeblock verwendet die gleiche Code-Formatierung wie überall sonst auf der Seite. Das Modul hat wie alle anderen Module eine Leiste oben. Der Button hat den globalen Button-Stil. So etwas.
Ich habe einen gepunkteten Rahmen um die „Leinwand“ gelegt, da ich dachte, das verleiht ihr ein etwas bearbeitbares/zielgerichtetes Gefühl.
Das JavaScript
Jetzt müssen wir diese Sache zum Laufen bringen. Das bedeutet…
- Wenn der Button geklickt wird…
- Führe den Code aus
- Inkrementiere den Schritt
- Wenn es der letzte Schritt ist, richte alles für den Neustart ein
Die Struktur
Das JavaScript, das wir schreiben, ist im Wesentlichen ein „Modul“. Alles hängt zusammen. Wir sollten ihm eine Struktur geben, um das offensichtlich zu machen. Der beste Weg, den ich kenne, ist, ein Objekt zu erstellen, das alles enthält.
var TreehouseAd = {
// Everything related to this ad goes in here.
};
Das sieht normalerweise ungefähr so aus
var TreehouseAd = {
importantVariableOne: "foo",
importantVariableTwo: "bar",
init: function() {
// The one function that will get called to kick things off
this.bindUIActions();
},
bindUIActions: function() {
// bind events here
// have them call appropriately named functions
$("button").on("click", function(event) {
TreehouseAd.doSomething(event, this);
});
},
doSomething: function(event, target) {
// do something
}
};
TreehouseAd.init();
Die Verwendung einer solchen Struktur führt zu lesbarerem, besser testbarem Code und fühlt sich einfach insgesamt besser an als ein Haufen disparater Funktionen und Variablen (Spaghetti).
Die „Schritte“
Wir werden fünf Schritte haben. Das heißt, die verschiedenen jQuery-Teile, die ausgeführt werden und verschiedene Dinge mit unserer Anzeigen-Leinwand tun.
- Füge der Verknüpfung die Klasse „button“ hinzu
- Bewege sie ein wenig nach oben (demonstriert Animation)
- Ändere den Text des Buttons
- Füge ein neues Element hinzu
- Füge dem gesamten Anzeige-Element eine Klasse hinzu, um ihm ein fertiges Aussehen zu verleihen
Wir müssen auch zwei Dinge mit jedem Code-Schnipsel tun
- Anzeigen
- Ausführen
Wir möchten den Code nicht an zwei separaten Stellen pflegen, also tun wir das einfach nicht. Wir speichern die fünf verschiedenen Code-Schnipsel in einem Array (geben ihm eine Struktur, anstatt fünf separater Variablen). Jeder Eintrag im Array ist ein Code-String. Wir können diesen String abrufen und anzeigen oder abrufen und eval() ausführen.
var TreehouseAd = {
...
codeText: [
"code for step 1",
"code for step 2",
...
],
...
}
Das endet dann so
codeText: [
"treehouse = \n\
$('#treehouse-ad')\n\
.addClass('button');",
"treehouse.animate({\n\
top: '-40px'\n\
});",
"treehouse.text(\n\
'Learn jQuery at Treehouse!'\n\
);",
"$('<div />', {\n\
id: 'tagline',\n\
text: "Hi, I'm Mike."\n\
}).insertAfter(treehouse);",
"$('#canvas')\n\
.addClass('all-done');\n\n\
console.log('Thanks for playing!')"
],
Beachte all die Schrägstriche und „n“ am Ende der Zeilen? Ein String, der in JavaScript mit „\“ endet, bedeutet nur „dieser String wird in der nächsten Zeile fortgesetzt.“ Er erscheint nicht im String selbst. „\n“ bedeutet „neue Zeile“, in unserem Fall brauchen wir das, damit der Code korrekt formatiert ist, wenn wir ihn in das <pre>-Element einfügen. Er hat keine Auswirkungen auf eval().
Die Animation
Um visuell klarzumachen, was passiert, wenn man auf den „Code ausführen“-Button klickt, dachte ich, es wäre eine coole Idee, wenn der Code nach oben gleitet und auf der „Leinwand“ verblasst. Die Schritte werden dann zu
- Erstelle eine Kopie/Duplikat des Code-Bereichs direkt über dem vorhandenen
- Animierte Position nach oben, während sie verblasst
- Entferne die Kopie, wenn fertig
- Warte, bis die Code-Text-Änderung erfolgt, nachdem die Animation abgeschlossen ist
Ich betrachte diese Aktion als eine „Funktionseinheit“. Das bedeutet, sie verdient eine eigene Funktion als Teil unseres Objekts.
var TreehouseAd = {
...
codeArea: $("#code-area"),
delay: 400,
animateCode: function() {
this.codeArea
.clone()
.addClass("clone")
.insertAfter(this.codeArea)
.animate({
top: "-60px",
opacity: 0
}, TreehouseAd.delay, function() {
$(".clone").remove();
});
},
...
};
Die Innereien
Der endgültige Code sieht so aus. Er enthält die Logik für die Ausführung des Codes und das Zurücksetzen und so weiter.
var TreehouseAd = {
step: 0,
delay: 400,
codeArea: $("#code-area"),
counter: $("#step-counter"),
init: function() {
this.bindUIActions();
},
codeText: [
"treehouse = \n\
$('#treehouse-ad')\n\
.addClass('button');",
"treehouse.animate({\n\
top: '-40px'\n\
});",
"treehouse.text(\n\
'Learn jQuery at Treehouse!'\n\
);",
"$('<div />', {\n\
id: 'tagline',\n\
text: "Hi, I'm Mike."\n\
}).insertAfter(treehouse);",
"$('#canvas')\n\
.addClass('all-done');\n\n\
console.log('Thanks for playing!')"
],
bindUIActions: function() {
$("#run-code").on("click", function(e) {
e.preventDefault();
TreehouseAd.animateCode();
TreehouseAd.runCode();
});
},
animateCode: function() {
this.codeArea
.clone()
.addClass("clone")
.insertAfter(this.codeArea)
.animate({
top: "-60px",
opacity: 0
}, TreehouseAd.delay, function() {
$(".clone").remove();
});
},
runCode: function() {
setTimeout(function() {
if (TreehouseAd.step < 5) {
eval(TreehouseAd.codeText[TreehouseAd.step]);
TreehouseAd.step++;
TreehouseAd.counter.text((TreehouseAd.step+1) + "/5");
TreehouseAd.codeArea.text(TreehouseAd.codeText[TreehouseAd.step]);
}
if (TreehouseAd.step == 6) {
// reset canvas
treehouse
.text("Team Treehouse!")
.removeClass()
.removeAttr("style");
$("#tagline").remove();
$("#canvas").removeClass("all-done");
$("#run-code").text("Run Code");
TreehouseAd.step = 0;
TreehouseAd.codeArea.text(TreehouseAd.codeText[TreehouseAd.step]);
TreehouseAd.counter.text((TreehouseAd.step+1) + "/5");
}
if (TreehouseAd.step == 5) {
$("#run-code").text("Start Over");
TreehouseAd.codeArea.text("");
TreehouseAd.counter.text("Done!");
TreehouseAd.step++;
}
}, TreehouseAd.delay);
}
};
TreehouseAd.init();
Das Ende
Noch einmal, die Demo auf CodePen
Du kannst auch eine modifizierte Version davon live auf dieser Seite sehen! Wir haben sie schon ein paar Mal aus Spaß geändert. Wahrscheinlich werden wir das auch weiterhin tun. Vielen Dank an Treehouse, dass ich solche Spaßprojekte machen darf. Schaut sie euch an, wenn ihr ein Fan von CSS-Tricks seid.
Das ist brillant! Ich kann mir vorstellen, dass Leute die gleiche Idee für ihre Websites übernehmen, um mit Besuchern zu interagieren und sie länger auf der Website zu halten. Und ja, ich werde es auch auf meiner Seite ausprobieren.
Warum den Code nicht als Funktionen im Array speichern und dann
.toString()aufrufen, um den Code zu extrahieren?Hier ist ein kurzes Beispiel
http://codepen.io/JosephSilber/pen/xAuEk
Sieht für mich nach einer großartigen Lösung aus!
Wenn ich darüber nachdenke, gibt es ein Problem mit dieser Lösung: Man kann das JS nicht minifizieren.
Du musst die Funktion und den angezeigten Text separat pflegen.
Ich verwende diese Art von Struktur inzwischen für meinen gesamten JS-Code und finde ihn viel besser.
Eine Sache, die ich gerne mit meinem Code mache, die ich von anderen übernommen habe, ist die Verwendung eines „settings“-Objekts, um zwischengespeicherte Elemente und Werte zu speichern. Anstatt also nur eine Reihe von nackten Variablen am Anfang zu deklarieren, sieht es ungefähr so aus:
So kann ich, wie in der
init()-Funktion geschehen, auf alle diese Einstellungen über „s.whatever“ zugreifen. Der einzige Nachteil ist, dass die Einstellungen am Anfang jeder Funktion mit „this.settings“ definiert werden müssen.Ich weiß nicht, ob das der beste Weg ist, so etwas zu machen, aber ich finde, es spart eine Menge Zeichen, sodass man nicht jedes Mal „ModuleName.variableName“ schreiben muss, wenn man ein zwischengespeichertes Objekt benötigt.
Sei vorsichtig mit diesem globalen Objekt. Du musst das richtig deklarieren.
Ja, das stimmt. Mein Beispiel war mehr oder weniger theoretisch. Nicht zum Kopieren und Einfügen gedacht.
Ich mache das normalerweise ungefähr so am Anfang
Und in diesem Sinne sollte der andere Code „var ModuleWhatever…“ sein, da dies ebenfalls global war, wie ich es geschrieben hatte.
In diesem Fall gibt es keinen Grund für
s = this.settingsam Anfang jeder Funktion.Sobald du das in deiner
init-Funktion getan hast, ist es in deinem gesamten Modul verfügbar, da alle Zugriff auf die übergeordnete closure haben.Ja, du hast Recht… Hmm, das macht es tatsächlich viel einfacher. Ich glaube, ein Teil meines alten Codes hat ein paar Redundanzen! :) Danke, dass du darauf hingewiesen hast.
Ziemlich cool… aber dann hast du dieses globale „s“, das mir Angst macht. Ich glaube, ich bevorzuge die wiederholte
s = this.settings;im Allgemeinen.@Chris Ich glaube nicht, dass wir hier den globalen Namespace verschmutzen. Wir werden all das in seiner eigenen closure haben.
Stimmt.
Ich liebe diese Anzeige wirklich. Ein großer Teil meiner Arbeit besteht darin, unsere Anzeigen interessanter zu gestalten und Statistiken über unsere Anzeigen auszuwerten. Deine Anzeige ist interaktiv, sie ist eingängig, sie ist sehr gut auf die richtige Zielgruppe zugeschnitten… Sie ist praktisch perfekt. Gute Arbeit, wie immer. :)
Auch ich habe in letzter Zeit einen Großteil meines Javascripts so strukturiert. Aber es gab auch einige Dinge, die ich darin noch nicht bedacht hatte. Eine Sache, mit der ich mich in letzter Zeit herumgeschlagen habe, ist die Einbindung von jQuery in meine Funktionen/Module.
Offensichtlich könnte es keinen Sinn machen, wenn ich weiß, dass jQuery da sein wird, aber ich habe trotzdem aus irgendeinem Grund damit gekämpft, mich darauf zu verlassen, ob jQuery da ist oder nicht.
Ich wäre daran interessiert, die Statistiken für diese Anzeige zu sehen. Welcher Prozentsatz durchläuft alle fünf Schritte und klickt auf den Button, welcher durchläuft alle fünf, klickt aber nicht, bleiben einige bei Schritt 3 stehen, usw. Wäre eine interessante Studie, da diese Form der Werbung sehr einzigartig ist.
Chris: Warum musst du
e.preventDefault()in diesem Codeblock deklarieren? Nur neugierig… Danke!Ich habe wahrscheinlich ein href=”#” auf dem Link, damit er einen richtigen Cursor und Hover-Effekt usw. hat. Aber dieser href würde zum Seitenanfang springen, wenn diese Zeile nicht da wäre.
Diese Zeile verhindert, dass der Browser dem href-Attribut des a-Tags folgt.
Großartig! Das hätte ich schon längst nutzen sollen ;). Danke!
Nicht um diese „interaktive“ Anzeige schlechtzumachen, aber es hat ewig gedauert, bis ich überhaupt verstanden habe, welcher Teil der Anzeige „interaktiv“ war, NACHDEM mir gesagt wurde, dass sie es ist. Das liegt daran, dass ich das grüne „Treehouse“-Feld gesehen habe und dachte, das sei die Anzeige und alles andere sei irrelevanter Inhalt. Es hat lange gedauert, bis ich den kleinen Absatz unter dem Code gelesen habe, in dem stand, dass er etwas mit Treehouse zu tun hat.
Ich glaube, das liegt am gepunkteten Rand um den Haupt-Treehouse-Anzeigenbereich. Er trennt ihn, und da ich es nicht gewohnt bin, interaktive Anzeigen zu sehen, brauche ich einen großen Sprung, um den Inhalt innerhalb des Kastens mit dem Inhalt darunter zu assoziieren.
Auch wenn es eine coole Interaktion sein mag, scheitert sie auf grundlegender Ebene, weil es so lange dauert, bis man herausfindet, dass sie irgendwie mit Treehouse zusammenhängt. Ich wäre neugierig zu sehen, ob die Anzahl der Leute, die mit dem Inhalt interagieren, es wert ist, ihn zu einer interaktiven Anzeige zu machen?
Danke für die Gedanken. Alles gute Dinge, die man in Zukunft berücksichtigen kann.
Ich habe das Gefühl, dass „Code ausführen“ ein ziemlich starkes, aktionsorientiertes Verb ist, und neugierige Leute werden wahrscheinlich klicken, um zu sehen, was passiert.
Es ist auch irgendwie gut, dass du den Treehouse-Bereich gesehen und den Inhalt darunter einfach überflogen hast. Es muss zuerst eine Anzeige sein und alles andere an zweiter Stelle.
Wie immer großartig, Chris!! Danke!
@Chris – „Code ausführen“ kann / wird auch einige, bereits übervorsichtige, Benutzer abschrecken… lol
@Louis – DANKE, dass du das „settings“-Objekt angesprochen hast…. Ich habe jetzt erkannt, dass ich auch ein paar Redundanzen zu beseitigen habe…. hahaha…