分类目录归档:javaScript

一种更好的网络图库图片预加载方法

流行的,或许并非最佳方式

网上的一个非正式调查展示了预加载一组图片的一个通用的标准方式。简化形式如下:

/* 'images' is an array with image metadata including a 'url' property */
for (var i = 0; i < images.length; ++i) {
    var img = new Image();
    img.src = images[‘url’];
}

这段代码遍历包含图片元数据的对象数组,为每个对象创建一个HTML Image对象,并为src属性设置url。一旦Image对象设置了src值,浏览器就会向服务器发起请求,并缓存返回的图片。

需要注意的是,浏览器请求是异步的。也就是说这段代码会遍历数组,每张图片几乎同时发起请求,并不需要等待服务器返回结果后顺序发起请求。对于现代浏览器而言,这段代码尝试并行下载4~8个张图片(当然如果来自不同域,会更多)。

并行下载的好处

网站通常会有一些资源,浏览器必须先下载后才能显式页面。HTML本身,一两个CSS文档,一些小的图片元素,字体,偶尔会有一些不可避免的JavaScript文件(需要在页面可以绘制前执行)。每一个文件通常都相当小,但是每个请求与服务器的往返都会导致延迟开销。虽然这种延迟通常很小,每个文档的延迟都是毫秒级的,但是如果浏览器需要等待一个请求完成后才能发起另一个请求的话,这些毫秒将依次累加,并迅速增至秒级,用户必须要等待这么长时间后才能浏览页面内容。如果可以同时发起所有请求,那么整体而言延迟时间会降低为一次的往返时延,从而使页面加载时间减少几秒钟。这并不会加快每个文档的实际下载速度,你仍受特定带宽的限制,所以4~8个的并行请求使下载速度降低4~8倍。但是总体下载速度的确加快了,因为你避免了连续的延迟开销。因为浏览器必须在下载了关键元素后才能绘制页面,避免顺序延迟时间累加意味着更快的页面绘制。

对预加载图片而言并非为一件好事

并行下载对于初始页面元素是非常有用的,因为浏览器在渲染页面前必须要先下载这些元素。一个CSS文件先于另一个CSS下载对于浏览器而言并无差别,因为浏览器需要两个文件都要下载后才能做其他事情。gallery里的图片预加载并非这种情况,你可以足够自信的预测哪个文件需要优先下载,你需要优先处理它,即使预加载的总体时间会稍长。

我网站的gallery的分析数据非常直观。虽然从一张图片切换至另一张图片的方式有多种,缩略图和上一张与下一张链接,90%的点击是在下一张链接上。几乎所有情况下,页面加载完成后的最关键因素是gallery里的下一张图片文件。如果使用标准的预加载方式,你无法控制这张图片何时加载。浏览器尝试加载gallery里的每张图片,以浏览器的最大并发请求组。这种技术很好的减少了gallery的总体加载时间,但也意味着加载最重要图片(下一张图片)的时间显著增长,因为它需要与其他并发请求竞争带宽。在相对缓慢的1.5Mbps DSL连接情况下,加载一张350K的图片需要2秒钟,浏览者有可能必须要等到4~6张图片加载完成后才能看到这种图片。也就是说gallery里的下一张图片可见前有一个潜在的12~15s的等待时间。有利的一面是这4~6张其他图片现在会被缓存起来,但是让用户盯着加载图片12秒钟,我们可能已经失去了这些用户。如果你以非「宽度连接」的方式测试你的网站的话,你可能会惊讶的发现,你的预加载器使你的网站对一些用户变得更糟。

一种更好的预加载方式

一旦理解了浏览者的行为,就可以设计一个预加载器,为大多数浏览者提供更好的体验。因为我知道几乎所有的浏览者是顺序浏览我的gallery,那么对我而言最好的策略是以相同的顺序加载图片。加载所有图片的总体时间可能会稍微长一些,因为我们会导致延迟开销累加,但此时的整体加载时间相对于页面的初始加载时间,并不是如此重要,因为在用户可以使用网站前,并不需要加载完所有的图片。我们只需要加载用户现在想看到的图片,javaScript如下:

function preload(imageArray, index) {
    index = index || 0;
    if (imageArray && imageArray.length > index) {
        var img = new Image();
        img.onload = function() {
            preload(imageArray, index + 1);
        }
        img.src = images[index][‘serving_url’];
    }
}
/* images is an array with image metadata */
preload(images);

注意:代码已做简化,生产环境代码会针对不同设备请求不同尺寸图片,并且会考虑用户进入gallery时非第一张图片的场景。

处理数组中第一张图片(index 0),添加onload事件处理函数,然后请求图片。只有当这张图片加载完成后,才会调用onload事件处理函数,然后为下一张图片做相同的操作,直到所有图片加载完成。

下图为使用Chrome开发者工具模拟2Mbps连接,下载序列和时间的比较:

并行预加载
(模拟的2Mbps连接,总体时间:24.01s,大图版本

串行预加载
(模拟的2Mbps连接,总体时间:25.44s,大图版本

标准预加载器的总体加载时间节省了1.5s。但是在这个案例中这个并非最重要的统计项。每个图表的最顶行表示gallery内下张图片的加载时间。在相对缓慢的连接环境下,使用标准的预加载器,页面加载完成14.27秒后,用户才能查看这张图片,即使这张图片处于第一行,主页面加载完成后就立即开始下载,这是因为它需要与图中所示的一些其他文档共享带宽。而顺序加载这些图片,会全带宽下载,于1.46秒内完成,提速接近于1000%。对于我们最常见的使用情况,这是一个巨大的进步。

这种策略需要关注非典型浏览者行为会产生什么效果?如果浏览者是另外10%用户,即点击了上一张图片链接或使用缩率图随机顺序跳转,会怎么样?因为我们的图片加载器每次仅加载一张图片,仍有可能并行加载另一张,并没有显著的性能损失。响应用户请求的JavaScript可以简单的请求图片,就像通常方式一样。因为它可能没有已经预加载和已缓存,浏览器会请求它,与当前正在加载的图片一起加载,如同让这条新请求加塞。由于在特定时间内仅有一张其他图片下载,浏览者的等待时间只比正常的等待时间稍长一点。在几乎所有情况下,相比在浏览器正在处理的6个并发请求之上再添加一个额外请求而言,这样的体验会更好。

另外一个好处是,由于浏览器一次仅处理一张图片,界面处于可响应状态。在标准预加载版本中,浏览器尝试同时下载和处理半打大型图片文档,这会消耗CPU时间,使页面的动画元素直到下载完成后才能处理。

避免过早预加载

在用户请求一个页面和浏览器可渲染这个页面之间会发生很多事情。这段时间内,你的用户只能坐在哪里看着一个空白页面。如果你珍惜你的用户,你应努力缩短这个时间。要做到这一点,需要尽可能快的传输浏览器所需的渲染页面的最少数据。你的预加载器不应参与其中。

至少对我而言,预加载代码最好放在window onload处理函数内,如果使用jQuery的:

$(window).load(function() {
    /* Preload code goes here */
});

 

————————————————————————————-

原文:A BETTER WAY TO PRELOAD IMAGES FOR WEB GALLERIES

async vs defer 属性

<script>元素的async和defer属性支持度已经不错了,是时候详细了解它们了。

图例

719530

<script>

<script>脚本不设置任何属性。HTML文档解析过程中,遇到script文档时,会停止解析HTML文档,发送请求获取script文档(如果是外部文档的话)。脚本执行后,才恢复HTMl文档解析。

936468

<script async>

设置async属性后,在HTML解析的同时,下载script文档。script文档下载完成后,HTMl解析会暂停,来执行script文档。

712163

<script defer>

设置defer属性后,在HTML解析的同时,下载script脚本。但只有在HTML解析完成后,才执行script文档。同时,defer属性保证脚本按照其在文档中出现的顺序执行。

460821

如何选用?

通常情况下,尽可能的使用async属性,然后考虑defer,都不适用时才不设置任何属性。选用规则:

  • 如果脚本是模块化的,并且不依赖其他脚本,那么使用async。
  • 如果脚本依赖其他脚本或被其他脚本依赖,那么使用defer。
  • 如果脚本比较小,并且被一个async脚本依赖,那么使用行内脚本,并放置在async脚本之前。

支持情况

IE9及以下浏览器在实现defer属性上存在糟糕的bug,比如无法保证脚本的执行顺序。如果需要支持<=IE9,不建议使用defer,如果执行顺序非常重要的话,不要使用任何属性。查看规范

后记

Chrome为了更快的页面加载,引入了两项JavaScript新技术:script streaming 和 code caching。简而言之,前者优化脚本文档的解析(Chrome 41),后者缓存编译后的代码(Chrome 42)。

streaming

ref:

  1. async vs defer attributes
  2. New JavaScript techniques for rapid page loads

one-device-pixel border


2014-12-31更新:
截至到IOS8.1,safari仍不支持@supports
待safari支持@supports, 就可以利用0.5px了!


2014-7-25更新:
1. 修正dpr = 1.5 机器下四角边框的缩放比例;
2. 修正右边框(rBor)的transform-origin为100%, 100%;
3. 添加对 dpr = 3 机器的支持; 通过以下机器验证:小米1(dpr = 1.5)、SAMSUNG S3(dpr = 2)、NEXUS 5(dpr =3) 测试地址:

Snip20140726_16


移动web开发,总避免不了1设备像素边框的问题。本文参考了half-point css border in ios 一文。

理想的

div{
   border:1px solid black;
}

@media (-webkit-min-device-pixel-ratio: 2){
 div{
    border-width:0.5px;
 }
}

仅有 Firefox和Safari 8 (introduced in OS X Yosemite)支持。twitter有位哥们听到这个消息时,已经不知所云。

psb

现实的

原理简单介绍:content属性与:before 及:after 伪元素配合使用,来插入生成内容,即所需边框。使用transform scale方法将生成内容的边框(或高度或宽度),缩小至合适大小(如0.5倍)。另外,处理好transform-origin值至关重要,下图可以帮助你更好地理解origin值。 transform-15 查看代码或查看此gist,并奉上demo继续阅读one-device-pixel border

创建无障碍的对话框

如今的web应用程序中,对话框如同在桌面应用程序中一样常见。我们使用较少的JavaScript和CSS就可以很容易的显示或隐藏一个元素,但很少会考虑对话框对可访问性的影响。多数情况下,它是可访问性的一个灾难。输入焦点未能正确处理以及屏幕阅读器无法感知内容变化。其实,使对话框可访问并非如此困难,你只需要理解几行代码的作用。

ARIA role

如果你想要屏幕阅读器用户感知到弹出了一个对话框,那么你需要学习一些ARIA role知识。ARIA role [1]为HTML元素提供了额外的语义,让浏览器与屏幕阅读器以更具描述性的方式进行沟通。ARIA定义了大量的角色,改变了屏幕阅读器感知页面中不同元素的方式。与对话框有关的角色有两个:dialog和alertdialog 。

在大多数情况下, 我们使用dialog。将一个元素的role属性设为此值,浏览器则会把该元素看作为一个对话框。

<div id="my-dialog" role="dialog">
    <-- Your dialog code here -->
</div>

当role属性值为dialog的元素可见时,浏览器会告知屏幕阅读器一个对话框已打开。这可以让屏幕阅读器用户意识到,他们已经不在页面的常规流中了。

对话框应有描述标签(label)。你可以使用aria-label属性来指明描述文本或者使用aria-labelledby属性来指明包含描述文字的元素的ID。示例如下:

<div id="my-dialog" role="dialog" aria-label="New Message">
    <-- Your dialog code here -->
</div>

<div id="my-dialog" role="dialog" aria-labelledby="dialog-title">
    <h3 id="dialog-title">New Message</h3>
    <-- Your dialog code here -->
</div>

在第一个例子中,aria-label属性用于指定一个仅用于屏幕阅读器的标签。当对话框的label无需可见时,你可以使用此方法。在第二个例子中,aria-labelledby属性用于指定包含对话框标签的元素的ID。由于对话框有一个可见的标签,关联复用比再重复一遍更妥。当对话框显示时,屏幕阅读器会报读对话框的标签。

alertdialog role是对话框的一种特殊类型,目的是为了吸引用户的注意力。你可以把它看作是当你尝试删除一些东西时弹出的确认对话框(confirmation dialog )。alertdialog的交互相比而言较少。它的主要目的是让用户感知到一个操作已执行。与此相比,dialog 可能是供用户输入信息的区域,比如写一封电子邮件或即时消息。

当一个alertdialog显示时,屏幕阅读器会查找描述文字来报读。建议使用aria-describedby来指定需要朗读的文本。这个属性与aria-labelledby 类似,其值是包含欲朗读内容的元素的ID。如果未指定aria-describedby,那么屏幕阅读器将试图找出能起描述作用的文本,往往会选择元素内第一段文本内容。例如:

<div id="my-dialog" role="alertdialog" aria-describedby="dialog-desc">
    <p id="dialog-desc">Are you sure you want to delete this message?</p>
    <-- Your dialog code here -->
</div>

此示例使用一个元素包含了描述文本。这样做可以确保在对话框显示时,会报读正确的文本。

即使你不设置这些额外的属性,仅对对话框设置适当的role,应用的可访问性也会得到极大地提高。

将焦点设置在对话框上

创建无障碍对话框的下一步就是管理焦点。当一个对话框出现时,焦点应在对话框内,这样用户才可以使用键盘继续浏览。焦点设置在对话框内的确切位置,在很大程度上取决于对话框本身的目的。如果确认对话框(confirmation dialog )内有一个“继续”按钮和一个“取消”按钮,那么你可以将焦点默认设置在“取消”按钮上。如果对话框是用来让用户输入文字的,那么你可以将焦点默认设置在文本输入框内。如果你实在不知道将焦点设在何处,将焦点设置在能代表对话框的元素上是个不错的选择。

由于多数情况下,我们使用<div>元素来表示一个对话框,那么可以将焦点默认设置在该<div>上。你需要将该元素的tabIndex属性设置为-1,这样这个元素才能获得焦点。这个属性值允许你使用JavaScript将焦点设置到该元素,但不会将该元素插入到正常的Tab键顺序中。也就是说用户将无法按TAB键将焦点设置在对话框上。直接在HTML中设置或通过JavaScript设置都可以。在HTML中设置:

<div id="my-dialog" role="dialog" tabindex="-1" aria-labelledby="dialog-title">
    <h3 id="dialog-title">New Message</h3>
    <-- Your dialog code here -->
</div>

通过JavaScript设置:

var div = document.getElementById("my-dialog");
div.tabIndex = -1;
div.focus();

一旦将tabIndex设置为-1,元素就可以调用focus(),就像任何其他的可聚焦元素一样。这样用户就可以按Tab键在对话框中导航了。

限制焦点(Trapping focus)

对话框的另一个可访问性问题是要确保焦点不能跳出对话框。一般来说,如果对话框是模态的,其焦点应无法逃脱对话框。当对话框打开时,如果按tab键将焦点设置到对话框背后的页面元素中,那么对于键盘用户来说将焦点重新返回到对话框内是相当困难的。因此,我们最好使用一些JavaScript以避免这种情况发生。

基本思路是使用事件捕获(event capturing)侦听focus事件,这种方法由Peter-Paul Koch[2]推广,如今已在JavaScript库中广泛使用。由于focus不冒泡(bubble),你无法在事件流的冒泡阶段捕捉到它。相反,你可以通过使用事件捕获方法捕获页面上的所有focus事件。之后,你只需确定获得焦点的元素是否在对话框中。如果没有,则将焦点设置在对话框上。代码是非常简单的:

document.addEventListener("focus", function(event) {

    var dialog = document.getElementById("my-dialog");

    if (dialogOpen && !dialog.contains(event.target)) {
        event.stopPropagation();
        dialog.focus();
    }

}, true);

代码监听document的focus事件,用以在目标元素接收到它们之前截获所有这类事件。假设对话框打开时,变量dialogOpen的值为true。当focus事件发生时,这个函数截获事件,并检查对话框是否是打开的,如果是的话,再检查接收焦点的元素是否在对话框内。如果两个条件都满足,则重新将焦点设置在对话框上。这样焦点就会在对话框的尾部和起始处循环。这就不会tab出对话框,键盘用户就很难再迷失方向。

如果你使用JavaScript库的话,focus事件委托的方法也可以实现同样的效果。如果不使用JavaScript库,同时需要支持Internet Explorer 8及更早的版本,可以使用focusin事件代替(译者注:focusin和focusout支持事件冒泡)。

恢复焦点(Restoring focus)

对话框的最后一个焦点难题:当对话框关闭时,将焦点返回至页面的主体部分。思路很简单:为了打开对话框,用户可能激活了一个链接或一个按钮。此时焦点转移到对话框中,用户完成一些任务后,然后退出对话框。焦点应该重新设回至为了打开对话框而点击的链接或按钮上,以便可以继续浏览网页。在Web应用程序中经常忽视这个问题,但效果是天壤之别。

与其他部分一样,少量代码即可实现效果。所有浏览器都支持document.activeElement ,返回当前具有焦点的元素。你只需获得这个值,然后显示对话框,关闭对话框时,将焦点返回到该元素。例如:

var lastFocus = document.activeElement,
    dialog = document.getElementById("my-dialog");

dialog.className = "show";
dialog.focus();

这段代码的重点是它记录了最后的焦点元素。这样一来,对话框被关闭时,将焦点设置在它上面:

lastFocus.focus()

总体而言,只会在你已有的对话框代码上增加几行代码即可实现。

退出对话框

最后一个问题是要为用户提供一个快速简便的方法来退出对话框。最好的办法是使用Esc键关闭对话框。这是对话框在桌面应用程序中的退出方式,所以用户非常熟悉这种方式。只需监听Esc键是否被按下,然后退出对话框,如:

document.addEventListener("keydown", function(event) {
    if (dialogOpen && event.keyCode == 27) {
        // close the dialog
    }
}, true);

Esc键的keyCode值是27,所以你只需在keydown事件中查找它。一旦监听到,关闭对话框并将焦点设置回之前的焦点元素上。

总结

从本文可以明显发现,创建一个可被屏幕阅读器和键盘用户轻松访问的对话框并不需要大量额外的代码。只需少量几行代码,你带给用户的不再是无尽的沮丧,而是令人难以置信的快乐。有很多Web应用程序在使用弹出式对话框,但很少能够正确处理这些问题。希望这篇文章对你创建自己的无障碍对话框有所启迪。

参考文献

WAI-ARIA (W3C)
focus和blur事件委托 by Peter-Paul Koch(Quirksmode)

本文译自Nicholas C. Zakas的新作《Making an accessible dialog box》,《JavaScript高级程序设计》就是他的作品。

[焦点管理]document.activeElement与模态对话框

简介

HTML5增加了辅助管理DOM焦点的功能。首先就是document.activeElement属性,这个属性始终会引用DOM中当前获得焦点的元素。元素获得焦点的方式有页面加载、用户输入(通常是通过按Tab键)和在代码中调用focus()方法。例如:

var button = document.getElementById("myButton");
button.focus();
alert(document.activeElement === button); //true

默认情况下,文档刚加载完成时,document.activeElement中保存的是document.body元素的引用。文档加载期间,document.activeElement的值为null。
另外就是新增了document.hasFocus()方法,这个方法用于确定文档是否获得了焦点。

var button = document.getElementById("myButton"); 
button.focus(); 
alert(document.hasFocus()); //true

通过检测文档是否获得了焦点,可以知道用户是不是正在与页面交互。

查询文档获知哪个元素获得了焦点,以及确定文档是否获得了焦点,这两个功能最重要的功能用途是提高web应用的无障碍性。无障碍web应用的一个主要标志就是恰当的焦点管理,而确切地知道哪个元素获得了焦点是一个极大的进步,至少我们不用再像过去那样靠猜测了。

浏览器兼容性

最初由IE4作为私有DOM扩展引入的,现在IE 4+、Firefox 3+、Safari 4+、Chrome和Opera 8+都支持。

应用实例

模态对话框的一个重要特点:

  • 对话框激活时,焦点应放置在对话框第一个可聚焦元素上;
  • 对话框关闭时,应将焦点交还至激活对话框的元素上。

HTML

<a href="#">酱油链接</a>
<button class="btn">open dialog</button>
<button class="btn">open dialog too</button>
<div class="dialog hidden" tabindex='-1'>
    <p>dialog content.</p>
    <button id="dismiss">close dialog</button>
</div>

CSS

.hidden{
    display:none;
}
.dialog {
    position: absolute;
    top:50%;
    left:50%;
    margin:-100px 0 0 -175px;
    width:350px;
    height:200px;
    background-color:#999;
    box-shadow:3px 3px 3px rgba(0,0,0,0.2), -3px 3px 3px rgba(0,0,0,0.2);
    text-align:center;
}

JQuery

var activeEl;
var dialog = $(".dialog");

function hideDialog(activeEl) {
    dialog.addClass("hidden");
    activeEl.focus();
}

function showDialog() {
    activeEl = document.activeElement;
    dialog.removeClass("hidden").focus();
}

$(".btn").on("click", function () {  
    $(this).focus();
    showDialog();		    
});

$("#dismiss").on("click", function () {  
    hideDialog(activeEl);
});

查看DEMO

 

REF:JavaScript高级程序设计(第3版)