最近在 CSS-Tricks 论坛,名为 Waffle 的用户 发布了以下 jQuery JavaScript 代码片段
$(function() {
$('.ContactArea').hide();
$('.Portfolio').hide();
$('.WebDesign').hide();
$('.AboutCoadin').hide();
$('li.Contact').click(function(){
$(".ContactArea").slideToggle();
});
$('li.PortfolioBtn').click(function(){
$(".Portfolio").slideToggle();
});
$('li.WebDesignBtn').click(function(){
$(".WebDesign").slideToggle();
});
$('li.AboutBtn').click(function(){
$(".AboutCoadin").slideToggle();
});
});
这位用户的实际问题是如何扩展他们的代码,以便在区域展开时向下滚动页面。但是,用稍微更有经验的眼光来看待这样的代码,有一些东西会脱颖而出,成为我们可以改进的地方。因为我敢打赌,你们中的一些人处于 Waffle 的水平,并编写了这样的代码,所以我认为我们可以用它作为一个案例研究来学习。
它有效,但是…
首先,Waffle 的代码没有 **损坏**,它运行良好,并且由于其简单的特性,我们最终做出的改进可能甚至不会带来明显的加速。但是,我们做出的改进应该使代码更易读、更高效、更具扩展性,并改进其他相关内容。此外,了解我们做出的改进将使我们将来在利害关系更高的场合成为更好的程序员。
我们需要 DOM 就绪语句吗?
第一行代码是
$(function() {
…这是 jQuery 的简写,意思是“当 DOM 准备好被操作时,运行此函数…” 这是一个安全网,例如,选择器不会在尚未解析的 HTML 上运行,让我们想知道 WTF。它也不会等到页面完全加载,这会使我们的脚本看起来很迟钝。
但问题是,我们应该在页面底部(在结束的 </body>
标签之前)运行这样的脚本,以便加载脚本不会阻碍页面渲染。如果我们在页面底部正确加载脚本,则不需要 DOM 就绪语句,因为浏览器在开始读取脚本之前就已经处理了所有这些 HTML。
多个选择器,一个 jQuery 对象
接下来的几行代码是
$('.ContactArea').hide();
$('.Portfolio').hide();
$('.WebDesign').hide();
$('.AboutCoadin').hide();
在这里创建四个单独的 jQuery 对象是不必要的,因为您可以在一个对象中使用多个选择器。这可以做到同样的事情
$(".ContactArea, .Portfolio, .WebDesign, .AboutCoadin").hide();
就像 CSS 选择器一样。但是,您可能确实需要四个单独的 jQuery 对象,以便您以后无需重新创建它们即可引用它们。在这种情况下,设置变量
var contactArea = $('.ContactArea').hide(),
portfolio = $('.Portfolio').hide(),
webDesign = $('.WebDesign').hide(),
about = $('.AboutCoadin').hide();
您还可以考虑向 HTML 添加一个类名,例如“initial-hide”,并将其应用于您希望在页面加载时隐藏的所有元素,并通过该类名进行选择,但这可能会违反语义。
ID
很难说,除非看到真实的网站,但我感觉这些区域,如“联系区域”和“作品集”,在页面上是完全唯一的区域,而不是重复出现多次的区域。完全唯一的区域非常适合使用 ID(而不是类)。这里的优势在于 ID 选择器速度更快。
$("#ContactArea"); // is faster than
$(".ContactArea");
关于 ID 的另一个重要事项是它们是自然的跳转链接,无论是否启用 JavaScript 都可以工作
<!-- This link will jump down the page ... -->
<a href="#ContactArea">Contact</a>
<!-- ... to ensure this element is visible -->
<section id="ContactArea"></section>
您可能实际上不希望页面向下跳转,但您可以通过 阻止默认行为 来使用 JavaScript 停止它。如果没有 JavaScript,您绝对希望同页导航是跳转链接。
选择合适的元素
接下来的代码片段有四个重复的部分
$('li.Contact').click(function(){
$(".ContactArea").slideToggle();
});
$('li.PortfolioBtn').click(function(){
$(".Portfolio").slideToggle();
});
$('li.WebDesignBtn').click(function(){
$(".WebDesign").slideToggle();
});
$('li.AboutBtn').click(function(){
$(".AboutCoadin").slideToggle();
});
首先要讨论的是实际被选择的元素。大概,标记如下所示
<nav>
<ul>
<li class="ContactArea"><a href="#">Contact</a></li>
<li class="PortfolioBtn"><a href="#">Contact</a></li>
<li class="WebDesignBtn"><a href="#">Contact</a></li>
<li class="AboutBtn"><a href="#">Contact</a></li>
</ul>
</nav>
jQuery 代码通过它们的类名选择列表项。这些是我的问题
- 它们并非都使用“Btn”约定。如果您坚持使用这样的类,您最好使其标准化。在如此狭小的范围内不坚持使用它将是其他更大规模的不一致的症状。
- 您不需要类来唯一地设置它们的样式。您可以使用 :nth-child 或 :nth-of-type。除非您需要支持非常旧的浏览器,在这种情况下,无论如何您都在使用 jQuery,并且可以使用它来伪造支持。
- 因为您不需要它们来设置样式,所以我想说您实际上根本不需要它们。
假设标记可以更改为使用 ID 而不是类,则可以将 HTML 重写如下
<nav>
<ul>
<li><a href="#ContactArea">Contact</a></li>
<li><a href="#Portfolio">Portfolio</a></li>
<li><a href="#WebDesign">Web Design</a></li>
<li><a href="#AboutCoadin">About</a></li>
</ul>
</nav>
这是一种更具语义的导航表示。没有不必要的类和作为指向相应内容的同页链接的锚链接。
不要重复自己
通常称为“DRY”。执行所有这些选择和 slideToggling 的四个语句非常重复。我们可以使用我们新的改进的 HTML 结构来改进它。
导航中这些锚链接的 href
属性类似于“#Contact”,这正是我们作为选择器字符串查找该元素所需的。非常方便! 让我们从链接中提取该 href
并将其用作选择器,这样,一个单一的点击处理程序就可以处理我们的所有四个链接
$("nav a").click(function(e){
e.preventDefault();
$($(this).attr("href")).slideToggle();
});
最终代码
我们的最终代码变得更加简洁
$("#ContactArea, #WebDesign, #Portfolio, #AboutCoadin").hide();
$("nav a").click(function(e){
e.preventDefault();
$($(this).attr("href")).slideToggle();
});
实际回答 Waffle 的问题
提出的问题实际上是“我还希望在按下这些按钮时页面滚动到这些 div” 我想说,你确定吗?这似乎是动画活动的奇怪组合。平滑滚动同页链接实际上比您想象的要复杂一些,但我有一些很棒的 可复制粘贴的代码。问题在于它在这种情况下不太适用,因为在单击链接时,要滚动到的元素实际上是隐藏的。只有在 slideToggle 完成后,元素才会显示并达到其最终大小。您必须延迟页面滚动,直到 回调函数 为止。可以做到,但很笨拙。
我做得好吗?
我最喜欢的在这个网站上发布博文的部分之一是,人们真的会花时间阅读它并提供高质量的建议。如果您有一些更好的方法来处理我上面提到的内容,我很乐意听取您的意见!
感谢您撰写了如此精彩的文章!
我强烈不喜欢的是,如果您点击“联系”,然后点击“网页设计”,两者都会显示。除了最后点击的那个之外,所有内容都应该隐藏。
您只需在调用切换之前添加一行代码,再次隐藏所有 ID 即可。
不不。我并不需要帮助,只是表达我的意见。
我同意。但是,如果您不仅重写了他们的代码,还重写了功能,那么案例研究就会失去价值。
非常有趣,克里斯。
谢谢。
我会考虑您所说的。
但是,如果您查看我的网站……http://coadin-internet.com
您可能会同意我的观点,即滚动到 Div(如果在这种情况下可行)将是一个好主意。
感谢您抽出时间对我的问题进行案例研究。
乔尼。
一些建议:(1)不要使用如此巨大的背景图片。1.66 MB?如果无法将其制作成较小的重复图像,至少将其制作成 JPG。(2)不要将按钮制作成带有嵌入背景图像文本的单元素列表。这对于语义没有任何贡献。这些背景可以轻松地重复成细条,但渐变 CSS 会更好。(3)您付出了努力使页面在禁用 JavaScript 的情况下也能正常工作,因此请将其贯彻到底。如果按钮没有任何作用,请不要让它们像按钮一样运作。(4)我认为,与其向下滚动,不如将按钮移到一侧,并在另一侧显示内容,每次只显示一个。这样可以避免滚动条,并使动画更有意义。使用克里斯的假设,即按钮位于单个 ul 中(“选择合适的元素”底部)对此有很大帮助,对 #2 和 #3 也有帮助。(4.5)将 Google 首字母大写,并保持您自己名称的一致性(“关于 Coadin”内容与页脚)。关注细节会产生差异。
如果您不想费心制作重复的背景图像或将按钮更改为 CSS/重复图像,您至少应该将页面通过 GTMetrix 运行,并使用它提供的图像的优化版本(或 Smushit 或 PunyPNG)。看起来那个大型背景图像可以无损压缩到 31.7KB
您还可以做另一件事
为了防止在点击其中一个按钮时内容略微跳到左侧(因为添加了滚动条),您可以添加:html { overflow-y: scroll}
这将始终在浏览器窗口的右侧添加一个滚动条(即使没有要滚动的内容),从而防止上述内容跳动。
还有一个视觉注释,尝试为内容框的白色背景添加一些透明度(您可以在 CSS 中使用 rgba 直接执行此操作,例如 { background-color: rgba(255, 255, 255, 0.5) },您可以在此处了解更多信息:https://css-tricks.org.cn/rgba-browser-support/)
您的表单没有任何标签或占位符文本来告知用户哪个字段需要哪些值。此外,如果您非常喜欢使用 jQuery 来构建您的网站,请添加一些表单验证以帮助防止 XSS 攻击等问题。但即使没有 title="" 属性来定义每个输入字段的悬停效果,也是糟糕的可用性。
不错的文章,克里斯。它真正展示了我去年在一次会议上了解到的内容,即从编写严格的声明式代码转向更具上下文性的编码。
在论坛等地方,人们发布了很多声明式内容,而您不知道他们的技能水平,我通常没有耐心帮助他们解决问题*并*解释编写代码的更智能方法。现在,我只需将他们引导到这篇文章即可!
使用您版本的导航有两个更好的理由。
1. 如果禁用 javascript,您仍然可以使用 css :target 选择器来显示或隐藏部分。
2. 如果禁用 javascript 和 css,例如在文本浏览器中,页面仍然可以工作,因为用户会跳转到所需的部分。
不太喜欢内容的闪烁。可以通过 css 'display:none' 来防止吗?
请参阅下方道格的评论以了解解决方案。
我一直都在寻找这种类型的帖子。理解一种语言的语法是一回事,而编写真正好的代码则是另一回事。关于如何编写基本的 jQuery 代码的信息很多,但很少有真正区分为什么您会做出这些选择以使代码变得更好的内容。这是一个如何使工作代码变得更好的绝佳示例。谢谢!
同意。我喜欢这个。另外,我喜欢您在此评论表单中添加的细节。
克里斯,干得好,代码看起来很棒。如果您想更进一步(不确定是否真的需要),我会做另外两件事。首先,我将使用“js”和“no-js”类来处理初始元素的隐藏。如果您的代码确实在页面底部运行,则在 jQuery 有机会隐藏它之前,您很有可能会看到内容闪烁
请记住,如果您使用的是 Modernizr,您只需包含 `no-js` 类,Modernizr 会处理类的交换。
然后,在 CSS 中
这样就不需要选择隐藏元素了。然后,我要做的最后一件事是使用 Delegate,这样在用户点击按钮之前绝对不会发生任何 DOM 遍历。这意味着,管理此操作所需的唯一 jQuery 就是委托命令,因为初始隐藏由 CSS 处理
这类似于仅使用“live”,但在这种情况下,我们将处理程序直接附加到 `document` 而不是首先进行选择(.live 的工作方式),因为我们的代码无论如何都在页面底部运行。
一个非常好的解决方案……做得好
这里需要注意的是,当我们使用 display:none 或 jQuery 的任何隐藏方法(hide、slideUp、fadeOut)时,它会使内容对屏幕阅读器不可访问。如果您希望内容对屏幕阅读器友好,我建议切换“hidden”类并使用 CSS 将元素定位到屏幕外。
它还有助于提高脚本的性能,因为 addClass()、removeClass() 比 hide()、show() 快一点。
您仍然可以使用动画,但请确保删除 jQuery 添加到元素的 style:display:none 并将其替换为您自己的类。
我认为 $(this).attr(“href”) 可以写成 this.href,速度更快。
在大多数现代浏览器中,是的,我认为 this.href 很好,但我认为某些 IE 7- 版本可能存在问题?jQuery 对此进行了一些规范化以确保其一致性。但我可能错了,我知道 this.id 具有非常广泛的浏览器支持,并且几乎总是最佳选择。
我经常想知道何时使用 delegate 比 click 更合适。在我看来,DOM 会发生变化时使用 delegate,不会发生变化时使用 click。
我了解冒泡部分,但出于某种原因,我认为 delegate 需要消耗更多资源来监控事件。完全不靠谱吗?当它是结构性内容(例如 li > a 或 nav > a)时,是否始终使用 delegate?
后续
为什么
$(document).delegate(‘nav a’, etc)
而不是
$(‘nav’).delegate(‘a’, etc)
读完这篇文章后,我也有类似的反应:为什么一开始不使用 display:none 隐藏所有内容,从而节省一行 javascript 代码,并可能避免内容闪烁?
所以我滚动浏览评论,看看是否有人在我自己发布之前发布过它。当我读到你的评论时,它很有道理,但突然间我意识到为什么这个解决方案不好……
根据我阅读的所有内容,屏幕阅读器永远不会朗读使用 `display:none` 隐藏的内容。屏幕阅读器也不会执行 JavaScript 代码。这意味着通过使用 CSS 隐藏内容的方式(就像你和我之前想的那样),你实际上让这些内容对盲人不可见。
仅供思考。
Sean,如果我没记错的话,这些元素在屏幕阅读器上不会被隐藏。由于屏幕阅读器不会执行 JavaScript,HTML 元素的类将保持为“no-js”,因此不会被 CSS 规则隐藏(因为它们只针对 html.js)。
我自己也是个相当糟糕但基本上功能正常的程序员,我很欢迎更多这样的文章。
我也是,我也是。
很好的教程,Chris。不过有一个错误:类名和 ID 无法以大写字母开头(例如,#ContactForm 应该为 #contactForm)。
哈?根据谁说的?HTML 允许这样做。ID 必须以字母字符开头(不区分大小写),类名基本上可以是任何内容:https://css-tricks.org.cn/unicode-class-names/。
在 HTML5 中,它们甚至更加宽松,基本上可以包含除空格外的任何字符。这使得使用与 URL 匹配的 ID(如“/about”)变得很容易。
不错的文章,希望有更多案例研究 :)……
很棒的文章,我今天学到了一些东西 :P
好文章!当我快速浏览初始代码时,我的脑海里已经开始进行和你最终做出的相同的改进。(添加一些字符串操作和/或 DOM 遍历,这种模式也适用于获取特定的类或其他元素。)你为此提供的代码片段看起来非常高效。
我最近开始大量使用从导航元素内的锚标签中获取选择器,并使用匹配的 ID 操作相应元素的模式。(添加一些字符串操作和/或 DOM 遍历,这种模式也适用于获取特定的类或其他元素。)你为此提供的代码片段看起来非常高效。
需要注意的是,这在 IE7 中不起作用,因为 $(this).attr(“href”) 将返回主机、路径以及任何查询字符串和 ID/哈希值。
所以你很可能需要做类似的事情…
$(“nav a”).click(function(e){
e.preventDefault();
var href = $(this).attr(‘href’);
href = href.substring(href.indexOf(‘#’));
$(href).slideToggle();
});
不错!我希望看到更多这样的帖子。我认为我在 jQuery/JavaScript 方面处于中等水平,我了解其中的一些想法,但不知道其他的。我今天学到了一些东西,这总是好的。谢谢 Chris!
好文章,这些规则非常有用,尤其是在大型项目中。
太棒了,Chris。通过查看示例代码并学习如何通过详细的解释对其进行优化,这是一个很棒的方法。我认为发布这样的内容非常棒,这样我们都可以改进并推动更高的编码标准。再次感谢。
为什么不使用 Head JS(http://headjs.com/)而不是将脚本标签放在底部以进行优化?
它还会将 html 元素上的 .no-js 类替换为 .js(因此无需使用 Modernizr)。它太棒了。
这很酷,感谢分享。我认为脚本加载是一个非常大的话题,它超出了少量代码清理的范围,不想吓跑那些只是想学习一些技巧的人。
我自己一直在使用 head.js;非常喜欢它!
出色的工作,我真的很喜欢这篇文章。
它实际上正是我正在进行的小项目中正在寻找的东西。
我刚刚分解了代码以使其适合页面的不同区域,它在 Chrome/Firefox 上运行良好,但在 IE(8)中,#content 框在每次单击时都会不断调整大小。
http://saassociates.webtodesigns.com/services.html
(点击特色项目)
你知道为什么吗?
我认为你没有按你的本意设置变量。
这将“缓存”选择器到一个变量中
var mySelector = $('#mySelector'),
myOtherSelector = $('#myOtherSelector');
然后你可以重复使用它们
mySelector.hide();
myOtherSelector.show();
在这个特定案例中,我看不到缓存 hide 函数的理由。
我还记得,preventDefault() 需要在自定义函数的末尾调用,而不是开头。
我确实这样做了。我故意这么做的。jQuery 的链式调用将在 .hide() 被调用后返回相同的选择器。因此,没有必要将其设置为变量,然后在其上调用函数,你可以将其组合到一个语句中!
太棒了;我以前不知道!很高兴我问了!
(也许“问”这个词用错了……我想我应该是说很高兴我提了出来);-)
这就是编程的艺术!你可以让代码工作,或者你可以创造艺术;)
我喜欢压缩脚本。当你可以用 5 行代码代替 15 行时!
Nico 说得好…
你好,
如何优化 jQuery 代码以使其对搜索引擎友好?…下次再见。
哇,非常酷的文章,作为一个 jQuery 新手,我在这里学到了很多东西。也许你可以定期做这个?看到这里所有不同的解决方案不断发展真是太好了,谢谢 Chris 和所有发表评论的伙伴们。
干杯,David
感谢分享,我不知道你可以像在第一行 $( #xxxx, #aaaa, #bbb).hide() 中那样对元素进行分组。
*咳嗽*
实际上,要回答 Waffle 的问题
$($(this).attr("href")).slideToggle("slow", function(){
//在此处滚动
});
不过,由于我不是核心 JS 高手,我建议使用以下插件进行实际的滚动(除非你已经是高手了)http://plugins.jquery.com/project/ScrollTo
很棒的文章,Chris。我确实从中学到了很多。
谢谢,Chris!我仅仅通过这个小小的案例研究就学到了很多东西。我很乐意看到你做更多这样的研究,这是一个很棒的想法!
因为我是 jQuery 新手,所以帮不上忙..但文章很棒!继续做好工作!
很棒的文章,Chris!
有趣的是,逻辑看起来如此简单明了。将选择器与 CSS 类似地进行组合,并创建简短且可重用的代码(类似于 OOPHP)。然而,我想因为我对 JavaScript 还比较陌生,所以我经常犯这些粗心的错误。感谢这篇文章。这正是我让我的代码更有效的需要。
附注 - (再次出现新手时刻)
从未想过要使用 e.preventDefault(); -> 我太喜欢这个了。