访问此网站的人可能很清楚: Treehouse 是我们主要的赞助商。 我喜欢这一点。 不仅因为他们提供结构化的学习,与我在这里提供的学习资料相辅相成,而且因为拥有一个主要的赞助商意味着我们可以花更多时间以有趣的方式将广告整合到网站中。
在与 Treehouse 的各位交谈时,他们告诉我他们有一些值得推广的新 jQuery 内容。 我想创建一个小的交互式区域,让大家对 jQuery 的功能有所了解,并激发他们学习更多关于它的兴趣。
让我们来看看它是如何构建的。
想法
- 有一个“画布”(不是
<canvas>
,只是一个发生事情的区域) - 有一系列五个简单的 jQuery 代码片段
- 单击按钮,代码运行,在画布上发生一些事情
- 您可以看到将要执行的代码
- 它应该直观地表明您看到的代码正在运行并进行更改
- 一直以来,画布区域都是 Treehouse 的可点击广告(用户不需要与 jQuery 内容交互,它仍然只是一个广告)
- 完成后,您可以从交互步骤重新开始

以下是最终的结果。 演示 在 CodePen 上
HTML 代码
相关部分已添加注释
<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.cn/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>
CSS 代码
这里没什么有趣的,所以我们不要过多关注它。 本质上,我让广告重复使用 CSS-Tricks 上已经使用的样式,因此广告看起来很和谐。 代码块使用与网站其他地方相同的代码样式。 模块顶部有一个条形,就像所有其他模块一样。 按钮是全局按钮样式。 就是这样。
我在“画布”周围放置了一个虚线边框,我认为这会给它一种可编辑/目标的感觉。
JavaScript 代码
现在我们需要让它工作。 这意味着……
- 单击按钮时……
- 执行代码
- 递增步骤
- 如果这是最后一步,则设置重新开始的步骤
结构
我们编写的 JavaScript 代码本质上是一个“模块”。 它们彼此相关。 我们应该赋予它一些结构,使其一目了然。 我知道的最好的方法是创建一个包含所有内容的对象。
var TreehouseAd = {
// Everything related to this ad goes in here.
};
这通常会得到类似这样的结果
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();
使用这种结构可以让代码更易读、更易于测试,而且总体感觉比一堆零散的函数和变量(意大利面条代码)要好。
“步骤”
我们将有五个步骤。 就像我们针对广告画布执行的不同 jQuery 代码片段。
- 将“按钮”类添加到链接
- 向上移动一点(演示动画)
- 更改按钮的文字
- 添加一个新元素
- 将类添加到整个广告中,使其更加完善
我们还需要对每个代码片段做两件事
- 显示它
- 执行它
我们最好不要在两个不同的位置维护代码,所以我们不会这样做。 我们将五个不同的代码片段存储在数组中(赋予它一些结构,而不是五个单独的变量)。 数组中的每个项目都是一个代码字符串。 我们可以获取该字符串并显示它,或者获取该字符串并使用 eval()
执行它。
var TreehouseAd = {
...
codeText: [
"code for step 1",
"code for step 2",
...
],
...
}
最终得到这样的结果
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!')"
],
注意到行末的所有斜杠和“n”吗? 在 JavaScript 中,以“\”结尾的字符串只是意味着“此字符串将在下一行继续”。 它不会出现在字符串本身中。 “\n”表示“换行”,在本例中,我们需要它,以便在将其放置在 <pre>
元素中时代码能够正确格式化。 它不会影响 eval()
。
动画
为了直观地显示按下“运行代码”按钮时发生了什么,我认为让代码向上滑动并淡入“画布”将是一个很酷的想法。 因此,步骤变为
- 在现有代码区域的正上方创建一个克隆/复制代码区域
- 淡出时向上移动动画位置
- 完成后移除克隆
- 等待动画完成再更改代码文本
我认为此操作是一个“块”功能。 这意味着它应该作为我们对象的一部分拥有自己的函数。
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();
});
},
...
};
核心
最终代码如下所示。 其中包含了运行代码、重置以及所有其他逻辑。
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();
结束
同样,CodePen 上的演示
您也可以在这个网站上看到它的修改版本! 我们已经为乐趣而修改过它几次。 可能会继续这样做。 非常感谢 Treehouse 让我做这些有趣的事情。 如果您是 CSS-Tricks 的粉丝,请查看他们的网站。
太棒了! 我可以看到人们会将这个想法复制到他们的网站上,用于与访问者互动,并让他们在网站上停留更长时间。 而且,是的,我也将在我的网站上尝试它。
为什么不在数组中将步骤存储为函数,然后调用
.toString()
从中提取代码?以下是一个简单的示例
http://codepen.io/JosephSilber/pen/xAuEk
看起来对我来说是一个很棒的处理方法!
仔细想想,这个解决方案只有一个问题:您无法缩小 JS 代码。
您必须分别维护函数和显示文本。
我最近一直在我的所有 JS 代码中使用这种结构,并且发现它非常棒。
我喜欢在我的代码中做的一件事(我从其他人那里学到的)是使用一个“设置”对象来保存任何缓存的项目和值。 因此,与其在顶部声明一堆裸变量,不如做成类似这样的结构
因此,就像我在
init()
函数中做的那样,我可以使用“s.whatever”访问任何这些设置。唯一的缺陷是您必须使用“this.settings”在每个函数的顶部定义设置。我不知道这是否是做这类事情的最佳方式,但我发现它节省了大量字符,因此您不必每次想要缓存对象时都执行“ModuleName.variableName”。
只需小心使用全局变量。您需要适当地声明它
是的,没错。我的示例或多或少是理论性的。不适合复制粘贴。
我通常在顶部执行类似的操作
同样,其他代码应该是“var ModuleWhatever…”,因为正如我编写的那样,它也是全局的。
在这种情况下,在每个函数的开头就没有理由使用
s = this.settings
。在您的
init
函数中执行此操作后,它将在整个模块中可用,因为它们都可以访问父闭包。是的,你说得对……嗯,这实际上使它变得容易多了。我认为我的一些旧代码有几个冗余!:) 感谢您指出这一点。
很酷……但你拥有那个全局“s”,这让我害怕。我认为我更喜欢重复的
s = this.settings;
。@Chris 我认为我们不是在谈论污染全局命名空间。我们将在它自己的闭包中拥有所有这些
没错。
我真的很喜欢这个广告。我工作中很多时间都花在尝试让我们的广告更有趣以及评估广告统计数据上。你的广告具有交互性,引人入胜,非常适合目标受众……它几乎完美无缺。一如既往,干得好。:)
我最近也一直用这种方式构建我的 JavaScript。不过,我之前也有一些没有考虑过的事情。最近我一直与在函数/模块中放入 jQuery 作斗争。
如果我仅仅知道 jQuery 会在那里,它可能毫无价值,但我仍然出于某种原因一直在与依赖 jQuery 存在与否作斗争。
我很好奇地想了解这个广告的统计数据。有多少人完成了所有五个步骤并点击了按钮,有多少人完成了所有五个步骤但没有点击,有多少人停留在第三步等等。这将是一项有趣的调查,因为这种形式的广告非常独特。
Chris:为什么您需要在此代码块中声明 e.preventDefault()?只是好奇……谢谢!
我可能在链接上使用了 href=”#”,所以它具有适当的光标和悬停状态等。但是,如果那行代码不存在,该 href 会跳转到页面顶部。
该行代码可以防止浏览器按照 a 标签的 href 属性中的链接跳转。
太棒了!我应该早点使用它;)。谢谢!
我不想贬低这个“互动”广告,但即使被告知它是互动性的,我也花了很长时间才弄清楚广告的哪个部分是“互动性的”。这是因为我看到了绿色的“treehouse”框,并认为那是广告,而其他所有内容都是无关的内容。我花了很长时间才阅读代码下方的小段落,上面写着它与 Treehouse 有关。
我认为这是因为 Treehouse 广告区域的主要区域周围有一条点线边框。它将它分开,而且由于我不习惯看到互动广告,所以我需要一个很大的跨越才能将框内的内容与下面的内容相关联。
虽然它可能是很酷的互动,但它在基本层面上失败了,因为需要很长时间才能弄清楚它与 Treehouse 有关。我很想了解与内容互动的用户数量是否值得把它做成互动广告?
感谢您的想法。所有这些都是未来需要考虑的重要因素。
我觉得“运行代码”是一个非常强烈的动词类按钮,好奇的人可能会点击看看它会做什么。
你看到了 Treehouse 区域,并只是略过下面的内容,这也很不错。它首先需要是一个广告,其他所有内容都是其次的。
一如既往,棒极了,Chris!谢谢!
@Chris – “运行代码”也会吓到那里的一些已经过于谨慎的用户……哈哈……
@Louis – 感谢您提出“settings”对象……我现在意识到我有一些冗余需要清理……哈哈……