陈建华的博客
专注web开发
【chrome扩展】Chrome扩展的UI界面(第三章)
2014-11-29 23:33:58   阅读6104次

前两章我们所设计的扩展,使用的UI设计都非常简单。对于一个面向用户的产品,这样显然是不合适的。用户对一个程序的第一印象就是UI的设计,拙劣的UI设计完全可能将90%的用户挡在门外——即使功能设计得非常完美。

本章将专门讲解Chrome扩展的UI界面,通过Chrome提供丰富的界面API,我们可以设计出交互出色的扩展。



3.1CSS简述

CSS是Cascading Style Sheets的缩写,翻译过来叫做层叠样式表,一般简称为样式表,但通常大家还是习惯叫CSS。

最初的HTML很单一,甚至无法显示图片,随着使用范围越来越广泛,HTML支持的标签开始多了起来,所支持的样式也开始增多。但是把样式完全交给HTML去做不是一个好想法,因为HTML更侧重于页面的结构,于是在1994年CSS被提出。CSS旨在对HTML元素的外观加以描述,来提供更多更加复杂丰富的样式。

现在多数浏览器会默认使用一些样式,比如div元素默认会占据整行——两个div元素不会出现在同一行,而span元素则不是这样,这就是因为浏览器默认将div元素样式的display属性值设为了block。

浏览器这种为HTML元素附加默认样式的做法,大部分情况下是好的,但有时为了个性化设计,我们需要另外编写CSS来自定义HTML元素的外观。

01YqXQtw6Vt3.png

Chrome会自动为HTML元素附加margin和padding样式

我们来看看上图所示的页面在Chrome浏览器中的渲染结果。可以看到HTML元素并没有被指定样式,因为我们没有编写CSS。但是Chrome已经自动为文本框添加了margin和padding样式,这在外观表现上,会在文本框周围有一圈间隙,这样其他HTML元素不会与它挨得太紧。这种设计显然是出于好意,但有时我们需要更加灵活个性化的样式,这就是为什么在前面的例子中都会出现下面的代码。

* {
    margin: 0;
    padding: 0;
}

CSS的选择器在第1章第4节已经介绍过,在此就不再赘述。下面讲一讲CSS的基本语法。

CSS是一种描述型语言,它更像是一种陈述,而不是逻辑运算。CSS的语法形式如下所示:

选择器 {
    属性名: 属性值;
}

CSS的选择器非常灵活,更加高级的使用方法大家可以参考相关的书籍。CSS的属性名也非常丰富,涉及到尺寸、边框、边距、位置、层叠顺序、文字(包括颜色、字体、粗细等等)和背景等等。

CSS使用box模型处理元素的尺寸、边框和边距,下图展示了它们之间的关系。

01YqXSCzRlKq.png

CSS的box模型

那么margin和padding有什么区别呢?padding区域算元素的一部分,所以元素的背景样式同样也会适用于padding区域。比如元素背景颜色设定为灰色,padding区域的背景颜色也会是灰色的,就如上图所示的那样。

需要注意的地方是,虽然padding是元素的内边距,也算元素的一部分,但元素的高度和宽度却并不包含padding区域。

元素的margin、padding、height和width的单位一般为px,即像素,也可以使用百分百的形式,如50%。如果使用的是百分百的形式,所相对的是此元素指定了绝对尺寸的父系元素。比如下面的例子:

<div id="outer" style="width: 500px">
    <div id="inner">
        <div id="content" style="width: 80%">Hello</div>
    </div>
</div>

其中id为content的div元素的宽度为80%,这80%是相对id为outer的元素而言的。虽然content的直接父系元素为inner,但是由于inner并没有指定宽度,所以会继续向上寻找父系元素,直到找到定义了width的元素为止。如果所有的父系元素都没有指定,则这个值是相对于body的。

对于元素的位置,默认情况就像报纸排版一样,将一个板块设计好之后,下一个板块会接着排列。这种像瀑布一样的排列方式我们形象地称为HTML流(flow)。默认情况下元素的position属性值为static,元素排列在正常的流中。position属性还有另外的三个值,分别是absolute、relative和fixed。如果元素的位置属性为absolute,则它的位置是相对于除static定位以外的父系元素的,如果没有这样的父系元素,则相对于body;如果元素的位置属性为relative,则它的位置是相对于它默认在HTML流中位置的;如果元素的位置属性为fixed,则它的位置是相对于浏览器窗口的。

01YqXXtYw3JL.png

不同位置属性的元素的定位效果

上图中浅灰色的元素是所有元素的父系元素,它的position属性为relative。深灰色和黑色边框的元素position都是默认的static,所以它们按照HTML流的方式依次布局。黑色的元素拥有absolute的位置属性,并指定left为10px,top为10px,它的定位是相对于浅灰色元素的。

对于relative定位,更像是对原有定位的偏移。对于left来说,负值向元素的左侧偏移,正值向右侧偏移,right与其相反;对于top来说,负值向元素的上侧偏移,正值向下侧偏移,bottom与其相反。需要注意的是,relative定位所定义的偏移不会影响元素原本在HTML流中的位置,下图给出了说明。

01YqXY3vd707.png

relative定位所定义的偏移不会影响元素原本在HTML流中的位置

虽然中间深灰色的元素相对于HTML流中的位置产生了偏移,但它原本在HTML流中的位置却没有改变,所以并没有影响下面浅灰色元素的位置。

默认情况下,如果元素和元素有重叠的部分,在HTML文档中靠后的元素会被显示在上面。但是可以通过CSS的z-index属性改变层叠顺序,z-index的值大的元素会显示在z-index值小的元素下面。如果一个元素没有被指定z-index的值,则在Chrome中默认为0(注意,并非所有浏览器都是这样,比如IE默认为负无穷大)。position属性为static的元素(即没有指定position属性的元素)z-index的值会被浏览器忽略。

CSS还可以定义元素中文字的大小、字体和颜色等,高级的属性还可以定义文字之间的距离、段首缩进、文字阴影等特殊的效果,下面我们主要讲讲对文字大小、字体和颜色的控制。

CSS使用font-size属性控制文字的大小,font-size的值可以是固定值也可以是百分比。如果是百分比,则相对的是父系的文字尺寸。如果是固定值,常见的单位有px、pt和em,另外还有一些其他的单位,如in、cm、mm、ex和pc。px最好理解,就是像素,和其他属性一个道理;pt是印刷界的单位,这个单位与物理尺寸相对应,如果使用pt作为单位,则在任何设备上,显示出来的大小都是一样的;em是个相对的单位,它是相对于元素当前文字尺寸的,比如元素当前文字尺寸为16px,则font-size为2em,显示出来的文字大小为32px。另外也可以使用特定的常量来设定文字大小,如xx-small、medium和large等,smaller和larger则把font-size设置为比父元素更小和更大的尺寸。

文字的字体使用font-family属性控制,这个属性可以有多个值。浏览器优先使用靠前的值,但如果用户的系统中没有安装指定的字体,则浏览器就会考虑使用后面的值,如果所有指定的字体用户的系统中都没有,则浏览器使用默认字体。对于Windows操作系统,中文的默认字体一般是宋体。需要注意的是,如果字体的名称中包含空格,需要用引号将字体名包含,多个值之间用逗号隔开。

文字的颜色使用color属性控制,color的值常见的有三种方式,分别是颜色名、十六进制颜色值和rgba。除此之外还可以使用HSL和HSLA格式。颜色名有black、red等,网上可以找到一份比较全的颜色名列表。但是能用名称表示的颜色十分有限,多数情况还是需要用颜色值表示。用十六进制的颜色值表示颜色的方法,是一个#符号后面接着6位十六进制数值,这6位数值每两位为一组,从左至右分别代表红色、绿色和蓝色的强度,#000000代表黑色,#FFFFFF代表白色。有时我们会遇到用三位十六进制数值表示颜色的情况,这是颜色值的缩略表示方式,表示每组颜色的十六进制码两位相同,如#ABC和#AABBCC表示的颜色相同。rgba表示方式除了包含红绿蓝三种颜色强度外还包含不透明度。其中前三个数字表示色值,第四个数字表示透明度。表示色值的数字有效值为0-255的整数或百分比(百分比也可以表示成小数,如50%也可以用0.5表示),表示透明度的数字有效值为0-1的小数。比如rgba(255, 0, 0, 0.5)表示透明度为0.5的红色。

CSS可以通过font属性将多种和文字相关的属性连在一起作为值,这种方式对初学者来说不直观,但对于熟练的人是个节省时间的好办法。

另外不得不再提一下line-height这个属性。对于文字来说,它每行所占据的高度是它的大小决定的,默认情况下两行相邻的文字不会重叠,也不会离得太远。下图展示了通过调整line-height属性使得两行相邻的文字重叠。

01YqXZOKh6R4.png


调整line-height属性使得两个相邻行的文字重叠

当想让文字在元素中垂直居中时,就可以通过指定line-height与height相同而达到目的1。

1 也可以使用vertical-align属性控制垂直位置。

CSS还可以控制元素的背景颜色和背景图片。背景颜色通过background-color进行控制,值的形式与color属性相同。背景图片通过background-image进行控制,值为url(图片位置)。对于背景图片,往往还要结合background-repeat和background-position使用。前者是控制图片重复的方式,默认是平铺,还可以指定为repeat-x(横向重复)、repeat-y(纵向重复)和no-repeat(不重复)。background-position是控制背景图片的位置,值的形式可以是top、bottom、left、right和center的结合,比如top left为左上角,center left为左侧中间,如果只指定了一个值,则另一个值默认为center。也可以是x% y%的形式,同样是相对于父系元素尺寸的。也可以以像素为单位,如10px 20px为距左侧10像素,上侧20像素。

背景样式还有很多更加丰富的属性,如background-size,这些更加高级的属性留给感兴趣的读者自行研究吧 :)

最后再强调一次,本节只是对CSS的简述,如果想学好CSS还应参考相关更加专业的书籍和资料,本节的作用只是避免没有任何基础的读者阅读后面的内容有障碍。

3.2Browser Actions

Browser Actions将扩展图标置于Chrome浏览器工具栏中,地址栏的右侧。如果声明了popup页面,当用户点击图标时,在图标的下侧会打开这个页面1。同时图标上面还可以附带badge——一个带有显示有限字符空间的区域——用以显示一些有用的信息,如未读邮件数、当前音乐播放时间等。

1 如果没有足够的空间,会在图标的上侧打开。

下面将对Browser Actions中的图标、popup页面、标题和badge做详细介绍。


3.2.1图标

Browser Actions可以在Manifest中设定一个默认的图标,比如:

"browser_action": {
    "default_icon": {
        "19": "images/icon19.png",
        "38": "images/icon38.png"
    }
}

一般情况下,Chrome会选择使用19像素的图片显示在工具栏中,但如果用户正在使用视网膜屏幕的计算机,则会选择38像素的图片显示。两种尺寸的图片并不是必须都指定的,如果只指定一种尺寸的图片,在另外一种环境下,Chrome会试图拉伸图片去适应,这样可能会导致图标看上去很难看。

另外,default_icon也不是必须指定的,如果没有指定,Chrome将使用一个默认图标。

通过setIcon方法可以动态更改扩展的图标,setIcon的完整方法如下:

chrome.browserAction.setIcon(details, callback)

其中details的类型为对象,可以包含三个属性,分别是imageData、path和tabId。

imageData的值可以是imageData,也可以是对象。如果是对象,其结构为{size: imageData},比如{'19': imageData},这样可以单独更换指定尺寸的图片。imageData是图片的像素数据,可以通过HTML的canvas标签获取到。

path的值可以是字符串,也可以是对象。如果是对象,结构为{size: imagePath}。imagePath为图片在扩展根目录下的相对位置。

不必同时指定imageData和path,这两个属性都是指定图标所要更换的图片的。

tabId的值限定了浏览哪个标签页时,图标将被更改。

callback为回调函数,当chrome.browserAction.setIcon方法执行成功后,callback指定的函数将被运行。此函数没有可接收的回调结果。

下面来编写一个图标不停旋转的扩展。

首先在Manifest中定义如下browser_action:

"browser_action": {
    "default_icon": {
        "19": "images/icon19_0.png",
        "38": "images/icon38_0.png"
    },
    "default_title": "Turtle"
}

为了让图标动起来,需要一个background脚本在后台不停地换图标,这个脚本如下:

function chgIcon(index){
    if(index === undefined){
        index = 0;
    }
    else{
        index = index%20;
    }
    chrome.browserAction.setIcon({path: {'19': 'images/icon19_'+index+'.png'}});
    chrome.browserAction.setIcon({path: {'38': 'images/icon38_'+index+'.png'}});
    setTimeout(function(){chgIcon(index+1)},50);
}
chgIcon();

为了达到动态旋转的效果,我们需要制作多张图片连续替换,这和gif的工作原理是一样的。

如果你不想用这种费力的方法,可以只制作两幅图片,分别对应于19像素和38像素两个尺寸,在background中通过canvas绘图来动态改变图片角度,然后输出imageData。感兴趣的读者可以搜索HTML5 canvas了解更多,本文在此不做详细介绍。

本小节编写的扩展源码可以通过https://github.com/sneezry/chrome_extensions_and_apps_programming/tree/master/browser_actions_icon  下载到。

3.2.2Popup页面

Popup页面是当用户点击扩展图标时,展示在图标下面的页面。下图是“网易云音乐(Unofficial)”扩展的popup页面。

01YvTuDUuTcx.png


“网易云音乐(Unofficial)”扩展的popup页面

Popup页面提供了一个简单便捷的UI接口。由于有时新窗口会使用户反感,而popup页面的设计更像是浏览器的一部分,看上去更加友好,但popup页面并不适用于所有情况。由于其在关闭后,就相当于用户关闭了相应的标签页,这个页面不会继续运行。当用户再次打开这个页面时,所有的DOM和js空间变量都将被重新创建。

所以,popup页面更多地是用来作为结果的展示,而不是数据的处理。通常情况下,如果需要扩展实时处理数据,而不是只在用户打开时才运行,我们需要创建一个在后端一直运行的页面或者脚本,这可以通过manifest.json的background域来声明,具体请参考2.3节所讲述的内容。而popup页面获取后端运行的结果,可以通过扩展内部的通信接口来完成,具体请参考2.5节所讲述的内容。

下面来重点讲述一下popup页面的设计和需要注意的地方。

Popup页面是一个扩展与用户交互的窗口,这个窗口设计的好坏直接会影响到用户的使用体验,所以更应加以重视。

首先,popup页面会根据内容自动显示合适的大小,但还是建议给出页面中body元素的尺寸,主要原因有两点:第一,如果不指定页面尺寸,在用户点击图标的瞬间会打开一个很小的窗口,DOM渲染完毕后页面尺寸才会正常,这个小的细节可能会给用户带来不好的体验;第二,我们也应该设计尺寸可控的页面,这样才能更好地优化布局,同时又不会出现小屏幕设备无法完全显示的情况。考虑到部分小尺寸的上网本,建议popup页面的高度最好不要超过500像素。当然也可以先给出一个默认的500像素高度,之后再通过js获取当前设备屏幕的尺寸后,再决定是否需要更改这个高度。但请注意,一个默认的页面尺寸是必要的。

不要尝试模仿Chrome的原生UI。有的开发者为了使自己的扩展看上去更像Chrome的一部分,而去刻意模仿Chrome的原生UI,这样做并不值得鼓励。首先我们应该让用户一眼就能看出,哪些部分是Chrome自带的功能,哪些部分是来自第三方提供的扩展功能,混淆用户的判断不是一个好主意。另外如果用户使用的不是Chrome的默认主题,这种设计看上去将很不协调。

使用带有滚动条的DIV容器。指定body元素的尺寸后,如果页面内容过长,就会被撑开,导致高度不可控,这可不是我们想要的结果。一个可行的解决办法是通过带有滚动条的DIV容器来防止页面被撑开。通常需要设定DIV容器的高度为100%,overflow-y属性为auto,这样当不需要滚动条时,DIV容器将不会显示滚动条。

01YvTupKEpyq.png

设定body高度为200像素后不使用DIV容器和使用DIV容器的对比

设计一个更好的滚动条样式。每款浏览器都有自己默认的滚动条样式,一般情况下不需要去更改它们——有些浏览器也不允许我们去更改,但对于扩展的popup页面则是另外一回事了。首先我们并不是在设计一个网站,而是在设计一款程序,滚动条作为程序的一部分,应尽量和程序的整体风格保持一致;再者,Chrome默认的滚动条,对尺寸有限的popup页面来说显得过于臃肿。“网易云音乐(Unofficial)”扩展就有一个自定义的滚动条样式,这要比直接使用默认的滚动条强上百倍。关于如何通过CSS来自定义webkit内核浏览器的滚动条样式,可以通过http://css-tricks.com/custom-scrollbars-in-webkit/了解更多。

考虑屏蔽右键菜单。如果你是一个追求尽善尽美的开发者,也许不希望用户在你的扩展页面上点击右键时,会出现Chrome的默认菜单,取而代之的应该是你自己设计的菜单。你要做的就是屏蔽掉右键,同时通过DIV浮动层模拟出一个自己的菜单。但需要注意的是,由于这个模拟的菜单还是在popup窗口之内的DOM元素,所以它不会像系统菜单那样可以超越页面边界。在设计这个菜单时要考虑到会不会有部分被遮挡,在用户点击鼠标唤出菜单前,请先让一段代码决定这个菜单适当的显示位置。

使用外部引用的脚本。这并不只是popup页面需要注意的地方,实际上Google在之前较早的某个版本开始,就不再允许HTML和JavaScript写在一个文件里了。所以要通过<script src="script-path/script-name.js"></script>来引用外部的脚本,而不是将JavaScript代码直接写在<script>标签内。

不要在popup页面的js空间变量中保存数据。由于popup页面只在用户点击图标时才会开启,当用户关闭这个页面时就会停止,并没有一个从始至终的实例分配给popup页面。所以每当用户打开popup页面时,它都是崭新的,之前保存在变量中的数据都会消失。如果需要通过popup页面保存用户的数据,可以通过通信将数据交给后台页面处理,或者通过localStorage和chrome.storage将数据保存在用户的硬盘上。

3.2.3标题和badge

将鼠标移至扩展图标上,片刻后所显示的文字就是扩展的标题。

在Manifest中,browser_action的default_title属性可以设置扩展的默认标题,比如如下的例子:

"browser_action": {
    "default_title": "Extension Title"
}

在这个扩展中,默认标题就是“Extension Title”。还可以用JavaScript来动态更改扩展的标题,方法如下:

chrome.browserAction.setTitle({title: 'This is a new title'});

标题不仅仅只是给出扩展的名称,有时它能为用户提供更多的信息。比如一款聊天客户端的标题,可以动态地显示当前登录的帐户信息,如号码和登录状态等。所以如果能合理使用好扩展的标题,会给用户带来更好的体验。

标题我们已经清楚了,那么什么是badge呢?我们来看一幅图:

01YvzLOdJ5uQ.png


扩展的标题和badge

上图中,标有“Extension Title”的地方就是扩展标题,而标有“Badg”的地方就是badge。Badge是扩展为用户提供有限信息的另外一种方法,这种方法较标题优越的地方是它可以一直显示,其缺点是只能显示大约4字节长度的信息,这就是为什么上例中显示的是“Badg”而不是“Badge”。

目前来看使用badge比较典型的应用是音乐播放器,它们使用badge显示当前音乐播放的时间;另一些内容类的应用,如邮件、微博、RSS阅读器等,则显示未读条目。无论你打算用badge显示何种信息,请记住它只能显示4字节长度的内容。对于内容类的扩展,当用户未读条目足够多时,一般采用的解决方法是显示“999+”。

Badge目前只能够通过JavaScript设定显示的内容,同时Chrome还提供了更改badge背景的方法。如果不定义badge的背景颜色,默认将使用红色,就是上图显示的那样。

下面的代码显示了一个背景颜色为蓝色,内容为“Dog”的badge:

chrome.browserAction.setBadgeBackgroundColor({color: '#0000FF'});
chrome.browserAction.setBadgeText({text: 'Dog'});

对于背景颜色的设定,设定值可以是十六进制的字符串颜色码,如#FF0000代表颜色;也可以是rgba格式的数组,但需要注意的是其中的alpha变量的取值范围同样为0-255,这与CSS有所区别。

下面的例子使用rgba的定义方式,将背景设置为50%透明度的绿色:

chrome.browserAction.setBadgeBackgroundColor({color: [0, 255, 0, 128]});

最后需要注意的一点,就是badge目前还不支持更改文字的颜色——始终是白色,所以应避免使用浅颜色作为背景。

3.3右键菜单

当用户在网页中点击鼠标右键后,会唤出一个菜单,在上面有复制、粘贴和翻译等选项,为用户提供快捷便利的功能。Chrome也将这里开放给了开发者,也就是说我们可以把自己所编写的扩展功能放到右键菜单中。

要将扩展加入到右键菜单中,首先要在Manifest的permissions域中声明contextMenus权限。

"permissions": [
    "contextMenus"
]

同时还要在icons域声明16像素尺寸的图标,这样在右键菜单中才会显示出扩展的图标。

"icons": {
    "16": "icon16.png"
}

Chrome提供了三种方法操作右键菜单,分别是create、update和remove,对应于创建、更新和移除操作。

通常create方法由后台页面来调用,即通过后台页面创建自定义菜单。如果后台页面是Event Page,通常在onInstalled事件中调用create方法。

右键菜单提供了4种类型,分别是普通菜单、复选菜单、单选菜单和分割线,其中普通菜单还可以有下级菜单。连续相邻的单选菜单会被自动认为是对同一设置的选项,同时单选菜单会自动在两端生成分割线。下面的代码生成了一系列的菜单:

chrome.contextMenus.create({
    type: 'normal',
    title: 'Menu A',
    id: 'a'
});
chrome.contextMenus.create({
    type: 'radio',
    title: 'Menu B',
    id: 'b',
    checked: true
});
chrome.contextMenus.create({
    type: 'radio',
    title: 'Menu C',
    id: 'c'
});
chrome.contextMenus.create({
    type: 'checkbox',
    title: 'Menu D',
    id: 'd',
    checked: true
});
chrome.contextMenus.create({
    type: 'separator'
});
chrome.contextMenus.create({
    type: 'checkbox',
    title: 'Menu E',
    id: 'e'
});
chrome.contextMenus.create({
    type: 'normal',
    title: 'Menu F',
    id: 'f',
    parentId: 'a'
});
chrome.contextMenus.create({
    type: 'normal',
    title: 'Menu G',
    id: 'g',
    parentId: 'a'
});

上面的代码生成的菜单如下图所示。

01Yw2KLQvWTP.png

自定义右键菜单

我们还可以定义自定义的右键菜单在何时显示,比如当用户选择文本时,或者在超级链接上单击右键时。下面的代码定义当用户在超级链接上点击右键时,在菜单中显示“My Menu”菜单:

chrome.contextMenus.create({
    type: 'normal',
    title: 'My Menu',
    contexts: ['link']
});

contexts域的值是数组型的,也就是说我们可以定义多种情况下显示自定义菜单,完整的选项包括all、page、frame、selection、link、editable、image、video、audio和launcher,默认情况下为page,即在所有的页面唤出右键菜单时都显示自定义菜单。其中launcher只对Chrome应用有效,如果包含launcher选项,则当用户在chrome://apps/或者其他地方的应用图标点击右键,将显示相应的自定义菜单。需要注意的是,all选项不包括launcher。

有时我们不仅想在特定的情况下显示自定义菜单,还希望限定URL,chrome同样提供了匹配URL的选项。documentUrlPatterns允许限定页面的URL,比如我们可以限定只在Google的网站上显示自定义菜单;targetUrlPatterns和documentUrlPatterns差不多,但它所限定的不是标签的URL,而是诸如图片、视频和音频等资源的URL。

如果在创建菜单时,定义了onclick域,则菜单被点击后就会调用onclick指定的函数。调用的函数会接收到两个参数,分别是点击后的相关信息和当前标签信息。点击后的相关信息包括菜单id、上级菜单id、媒体类型(image、video或audio)、超级链接目标、媒体URL、页面URL、框架URL、选择的文字、是否可编辑(只针对text input和textarea等控件)、用户点击前是否被选中和当前是否被选中(只针对checkbox或radio)。完整的信息结构可以通过http://developer.chrome.com/extensions/contextMenus#type-OnClickData查看。

update方法可以动态更改菜单属性,指定需要更改菜单的id和所需要更改的属性即可。remove方法可以删除指定的菜单,removeAll方法可以删除所有的菜单。

下面我们来创建一个通过右键菜单使用Google翻译当前用户所选文本的扩展。我们希望只有当用户选择了文本才显示这个菜单,所以要将contexts的值设为selection。

chrome.contextMenus.create({
    type: 'normal',
    title: '使用Google翻译……',
    id: 'cn',
    contexts: ['selection']
});

下面来编写调用的函数。Google翻译可以通过http://translate.google.com.hk/#auto/zh-CN/{翻译文本}调用,所以只需要获取用户所选择的文本,同时打开这个URL就可以了。

function translate(info, tab){
    var url = 'http://translate.google.com.hk/#auto/zh-CN/'+info.selectionText ;
    window.open(url, '_blank');
}

现在我们把create函数补充完整,把调用函数添加进去:

chrome.contextMenus.create({
    type: 'normal',
    title: '使用Google翻译……',
    contexts: ['selection'],
    id: 'cn',
    onclick: translate
});

最后把这段代码写进background.js中,让扩展在浏览器启动后自动执行就可以了。

01Yw2MJYm4Ll.png

Google翻译扩展

但我们发现这样无法在菜单中动态显示用户所选择的内容,那么如何动态显示诸如用Google翻译“XXX”这样的菜单呢?首先要获取用户所选择的文本,可以通过下面的代码来实现:

window.onmouseup = function(){
    var selection = window.getSelection();
    if(selection.anchorOffset != selection.extentOffset){
        //do something
    }
}

那么这段代码在background中执行会成功吗?显然不能,因为background和当前页面并不在一个空间中,所以我们需要用content_script来注入脚本,对content_script不了解的读者可以参考2.1节的内容。content_script获取到用户所选文字后,就可以通过2.5节所讲述的内容,传递给后台页面。

01Yw4XhgQZzZ.png

改进后的Google翻译扩展

由于改进的部分不是本章的重点,所以就不详细讲解了,大家可以参考前面的章节1。完整的代码可以通过https://github.com/sneezry/chrome_extensions_and_apps_programming/tree/master/google_translate下载得到。

1 创建菜单时也可以直接使用%s表示选定的文字。

Chrome还提供了onClicked事件,虽然在create方法中可以指定点击时调用的函数,但对于Event Page只能通过onClicked事件调用函数。Event Page与一般的background类似,但它只按需加载,并不像background那样一直驻守后台。

3.4桌面提醒

之前的章节提到过利用标题和badge向用户提供有限的信息,那么如果需要向用户提供更加丰富的信息怎么办呢?Chrome提供了桌面提醒功能,这个功能可以为用户提供更加丰富的信息。

01Yw51gGGxKh.png

桌面提醒,图片来自http://developer.chrome.com

要使用桌面提醒功能,需要在Manifest中声明notifications权限。

"permissions": [
    "notifications"
]

创建桌面提醒非常容易,只需指定标题、内容和图片即可。下面的代码生成了标题为“Notification Demo”,内容为“Merry Christmas”,图片为“icon48.png”的桌面提醒窗口。

var notification = webkitNotifications.createNotification(
    'icon48.png',
    'Notification Demo',
    'Merry Christmas'
);

桌面系统窗口创建之后是不会立刻显示出来的,为了让其显示,还要调用show方法:

notification.show();

需要注意的是,对于要在桌面窗口中显示的图片,必须在Manifest的web_accessible_resources域中进行声明,否则会出现图片无法打开的情况:

"web_accessible_resources": [
    "icon48.png"
]

如果希望images文件夹下的所有png图片都可被显示,可以通过如下声明实现:

"web_accessible_resources": [
    "images/*.png"
]

桌面提醒窗口提供了四种事件:ondisplay、onerror、onclose和onclick。

除了用户主动关闭桌面提醒窗口外,还可以通过cancel方法自动关闭。下面的代码可以实现5秒后自动关闭窗口的效果。

setTimeout(function(){
    notification.cancel();
},5000);

由于桌面提醒界面可能将不再支持引入JS脚本,桌面提醒窗口与其他界面的通信本节不进行讲解。

桌面提醒已经被纳入了W3C草案,相关信息可以访问http://dev.chromium.org/developers/design-documents/desktop-notifications/api-specification查看。

除此之外,也可以通过Chrome提供的chrome.notifications方法来创建功能更加丰富的提醒框。

3.5Omnibox

Chrome和其他浏览器相比一个最大的区别就是地址栏——其实不仅仅是地址栏,而是一个多功能的输入框,Google将其称为omnibox(中文为“多功能框”)。我们熟悉的一个功能就是用户可以直接在omnibox搜索关键字,Chrome也将omnibox开放给开发者,这使得omnibox更加强大。

要使用omnibox需要在Manifest的omnibox域指定keyword:

"omnibox": { "keyword" : "hamster" }

同时最好指定一个16像素的图标,当用户键入关键字后,这个图标会显示在地址栏的前端。

"icons": {
    "16": "icon16.png"
}

Chrome会自动将这个图标渲染成灰度图标,而无需开发者指定一个灰度的图标,由于右键菜单等其他地方也会用到16像素的图标,所以应该指定一个彩色的图标。

Omnibox只提供了一个方法,就是setDefaultSuggestion,这个方法用来定义默认建议。对于这个默认建议用文字怎么讲解恐怕都不容易讲清楚,那么不妨来看一看设置了默认建议和不设置默认建议的对比:

01fjDGpX5s9Y.png

未设置默认建议和设置了默认建议的对比

上图中左侧为未设置默认建议,显示为“运行 XXX 命令:XXX”,这样显然看起来不够友好。右侧则用更加友好的方式显示查询当前美元价格。

默认建议会在用户输入keyword之后一直显示在地址栏下方并且紧挨着地址栏,所以设定一个默认建议是必要的,否则简单地显示“运行 XXX 命令:XXX”会让用户摸不到头脑。

Omnibox有四种事件:onInputStarted、onInputChanged、onInputEntered和onInputCancelled,分别用于监听用户开始输入、输入变化、执行指令和取消输入行为。其中执行指令是指用户敲击回车键或用鼠标点击建议结果。

onInputStarted(function(){console.log('Input started.')});
onInputCancelled(function(){console.log('Input cancelled.')});

上面的代码执行后,用户开始输入和取消输入时,都会在控制台记录相应日志。下面我们重点来讲一讲另外两个事件。

onInputChanged事件所承接的只有一个function类型的参数,这个function参数又有两个承接参数,第一个参数是字符串型,值为用户当前的输入值,第二个参数还是function型,用于返回建议结果,建议的结果为数组型数据,数组中的元素是建议结果对象。

chrome.omnibox.onInputChanged.addListener(function(text, suggest){
    suggest([{
        content: text,
        description: 'Search '+text+' in Wikipedia'
    }]);
});

onInputEntered事件同样只有一个function类型的承接参数,这个function有两个承接参数,第一个是用户输入的值,字符串型,第二个是对结果的建议打开方式,字符串型,但取值范围固定。

chrome.omnibox.onInputEntered.addListener(function(text, disposition){
    switch(disposition){
        case 'currentTab': //do something in the current tab
                 break;
        case 'newForegroundTab': //do something in a new tab and active it
                 break;
        case 'newBackgroundTab': //do something in a new tab
                 break;
    }
});

下面来制作一款实时查询美元价格的扩展。首先通过异步请求获取Yahoo上美元的价格,对这部分不熟悉的读者可以参考前面2.2节的内容。获取到数据后我们就要开始编写提供建议的函数了。

function updateAmount(amount, exchange){
    amount = Number(amount);
    if(isNaN(amount) || !amount){
        exchange([{
            'content': '$1 = ¥'+price,
            'description': '$1 = ¥'+price
        },{
            'content': '¥1 = $'+(1/price).toFixed(6),
            'description': '¥1 = $'+(1/price).toFixed(6)
        }]);
    }
    else{
        exchange([{
            'content': '$'+amount+' = ¥'+(amount*price).toFixed(2),
            'description': '$'+amount+' = ¥'+(amount*price).toFixed(2)
        },{
            'content': '¥'+amount+' = $'+(amount/price).toFixed(6),
            'description': '¥'+amount+' = $'+(amount/price).toFixed(6)
        }]);
    }
}
var url = 'http://query.yahooapis.com/v1/public/yql?'+
          'q=select%20Rate%20from%20'+
          'yahoo.finance.xchange%20'+
          'where%20pair%20in%20(%22USDCNY%22)&'+
          'env=store://datatables.org/alltableswithkeys&'+
          'format=json';
var price;
httpRequest(url, function(r){
    price = JSON.parse(r);
    price = price.query.results.rate.Rate;
    price = Number(price);
});
chrome.omnibox.onInputChanged.addListener(updateAmount);

大家可以对照前面所讲解的部分来看这段代码,代码中的每个部分都与前面的讲解有所对应。接下来编写用户执行指令时所运行的函数。

function gotoYahoo(text, disposition){
    window.open('http://finance.yahoo.com/q?s=USDCNY=X');
}
chrome.omnibox.onInputEntered.addListener(gotoYahoo);

此例中并没有理会disposition的取值,Chrome官方也指出disposition只是给出结果呈现的建议方式,而非必须遵循的方式,所以是否理会这个值由你自己说了算。

最后就像前面所说的那样,记得设定一个默认的建议,这样会使你的扩展看起来更加友好。

前面讲解默认建议的截图就是这个例子运行的结果,所以在此就不重复贴图了。本例的完整代码可以通过https://github.com/sneezry/chrome_extensions_and_apps_programming/tree/master/usd_price  下载,载入扩展后在浏览器地址栏中输入“usd”后按空格键或Tab键就可以使用。

3.6Page Actions

Page Actions与Browser Actions非常类似,除了Page Actions没有badge外,其他Browser Actions所有的方法Page Actions都有。另外的区别就是,Page Actions并不像Browser Actions那样一直显示图标,而是可以在特定标签特定情况下显示或隐藏,所以它还具有独有的show和hide方法。

chrome.pageAction.show(integer tabId);
chrome.pageAction.hide(integer tabId);

tabId为标签id,可以通过tabs接口获取,有关tab相关的内容将在后面进行讲解。

由于Page Actions和Browser Actions有大量相似之处,在此就不详细介绍了,还请读者参照前面3.2节的内容。









-----------------------------------------------------
转载请注明来源此处
原地址:#

-----网友评论----
1楼:yy 发表于 2018-07-20 11:20:38
像油猴添加新脚本,打开网页地址栏清空怎么做到的?
-----发表评论----
微网聚博客乐园 ©2014 blog.mn886.net 鲁ICP备14012923号   网站导航