在7yue的blog上看到fp10.1的表现让人兴奋不已,希望Adobe摆脱“很有压力,才有推力”的形象……
整体流程
终于说到整体流程上了。之前的文章一直在解说as中如何来做词法分析,js方面丝毫未提,更没说jssc到底在页面上是怎么工作的。现在,就来详细地解释一下。
环境需求:
- 现代浏览器(废话)
- 装有Adobe Flash Player插件,版本号为9或9以上
- javascript启用
- 页面中放入jssc.swf文件
大致流程:
- swf文件载入完毕
- 内嵌在swf中的js首先被执行,寻找页面中所有符合规则的pre节点
- 顺序遍历这些pre节点,提取每个pre节点的文本内容(即原始代码)
- 将原始代码传递给swf
- swf对传递来的原始代码进行词法分析,并生成一段结果html片段(即一串li节点)
- 将结果传递回js
- js对结果进行包装(一串li节点放入ol节点中,以及标题头、边距和复制等等)
- 将对应的原始pre节点隐藏(display:none)
- 将新生成的内容插入原始pre节点的前面
可以看出,js和as耦合的地方在哪里,以及它们之间是如何相互合作的。下面将对以上9个步骤细细说来,希望更多的开发者能提出改进意见~
1.载入swf
这里没什么好说的,在页面底部加入flash标签即可。值得注意的是不同浏览器中标签有所区别,理想情况下可以使用swfobject来插入标准的html标签。不过为了兼容以及简单,一般我是这样做的:
<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" id="jssc5" style="position:absolute;visibility:hidden;"> <param name="movie" value="jssc5.swf"/> <param name="flashvars" value="find=brush"/> <embed src="jssc5.swf" flashvars="find=brush" name="jssc5" type="application/x-shockwave-flash" style="position:absolute;visibility:hidden;"/> </object>
这里面有许多可配置的地方。
object的id和embed的name必须一致且唯一(应该是唯二才对),为了满足不同浏览器的需要。我设为jssc5,这也是默认值。如果和页面上某个节点有冲突需要修改,那么就需要传入参数修改配置。比如说变成jssc5_modify,就得在flashvars中这样做:
<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" id="jssc5_modify" style="position:absolute;visibility:hidden;"> <param name="movie" value="jssc5.swf"/> <param name="flashvars" value="find=brush&swf=jssc5_modify"/> <embed src="jssc5.swf" flashvars="find=brush&swf=jssc5_modify" name="jssc5_modify" type="application/x-shockwave-flash" style="position:absolute;visibility:hidden;"/> </object>
注意到param标签和embed标签中都得改,为了满足浏览器兼容需要……
同样,jssc会占用一个全局js变量名jssc(注意这个jssc是指变量名字符串,而前者是指软件),这是为了在js和as之间互相调用通信必须的。如果这个变量名和页面上的js变量有冲突,那么就会造成错误。所以它也可以进行修改配置,假如要改成jssc_js,就得在flashvars中这么做:
<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" id="jssc5_modify" style="position:absolute;visibility:hidden;"> <param name="movie" value="jssc5.swf"/> <param name="flashvars" value="find=brush&swf=jssc5_modify&js=jssc_js"/> <embed src="jssc5.swf" flashvars="find=brush&swf=jssc5_modify&js=jssc_js" name="jssc5_modify" type="application/x-shockwave-flash" style="position:absolute;visibility:hidden;"/> </object>
最后,还有两个可以配置的参数:css和url。前者是指新生成的结果节点的class名称,你可以用firebug看到默认的亦是jssc;后者是指定swf的路径,这是为了在非ie浏览器下做copy使用(ie可以直接调用api做复制功能),从这个意义上说,整个swf文件其实是有两种功能——词法分析和复制按钮。这么做是为了减少http请求,同时js融合在as里也是为了减少http请求。
2.内嵌的js执行
js首先会寻找页面上所有符合规则的pre节点,那么是什么规则的?默认情况下,pre的class名称如果这样开头,js就会认为它是正确的(其实并不一定要求是开头,出现在其它位置也可以,但为了良好的习惯推荐如此):
<pre class="brush:html"></pre>
这段被高亮的代码其实就是它本身。brush是键名,html是键值。假如想要高亮一段js代码,那么就需要更改键值:
<pre class="brush:js"></pre>
其它语言道理相同。另外,可以高亮的代码必须是jssc本身已经支持的语法种类,不支持的话也会被格式化,但没有高亮效果。
细心的人可能已经发现,brush键名和最开始那段代码中出现的相一致。没错,假如pre节点的class名称也有冲突,不能使用brush,要改成brush_modify,那么就要如此修改配置:
<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" id="jssc5_modify" style="position:absolute;visibility:hidden;"> <param name="movie" value="jssc5.swf"/> <param name="flashvars" value="find=brush_modify&swf=jssc5_modify&js=jssc_js"/> <embed src="jssc5.swf" flashvars="find=brush_modify&swf=jssc5_modify&js=jssc_js" name="jssc5_modify" type="application/x-shockwave-flash" style="position:absolute;visibility:hidden;"/> </object>
注意param和embed中要修改2次。
在找到所有符合规则的pre节点之后,js将这些节点的引用存储下来,然后一一遍历它们。下面的经过就是每次遍历一个pre节点时所发生的。
3.遍历提取内容
提取pre节点的内容很简单,即我们熟知的innerText。不过在不同浏览器中也有让人头疼的地方。以前在做jssc4的时候,firefox2版本的innerText容量有限制,如果代码过多,提取的内容就不完整。另外也有低版本浏览器不支持innerText的可能性发生。所以综合考虑我写了个如下的js方法:
function getText(node) {
var code = node.textContent || node.innerText;
if(!code && node.firstChild) {
code = node.firstChild.nodeValue;
}
return code || "";
}
4.传递内容给as
这是as定义好的接口,js和as可以通过ExternalInterface类来实现互调。不再多说。
5.as做词法分析
这是前几篇一直在说的内容,不再多说。
6.传递结果给js
同第4点。
7.js包装结果
生成的结果其实是一串li节点,js要做的就是创建一个ol节点,然后设置innerHTML为传递来的结果字符串。如此最基本的高亮代码就完成了。之后是设置标题、起始行数、边距、复制功能和鼠标移入高亮当前行背景。
其中边距是指ol节点的paddingLeft值,因为代码行数不一样,所以左边距一定是根据行数动态进行计算的。在js中我的公式如下:
oOl.style.paddingLeft = Math.max((line.length + 2) * 9, 30) + "px";
8.隐藏pre节点
最后是将本次循环的这个pre节点隐藏。注意,这里是隐藏不是删除!因为后面还要用到代码复制功能,所以只能隐藏不能删除!
9.插入结果
将包装好的结果插入本次循环pre节点的前面,这轮循环就算走完了,再继续下一轮循环。
结束语
好了,整个系列文章算结束了。欢迎大家提出好的建议和想法,或者直接参与到开发当中来,亦或以别的方式来参与(比如说做wordpress的插件)。如果你能做出比jssc更好的高亮插件,那么我也会放弃jssc,投入新的、更好的项目中去的——这话说的好假:)
接上篇,继续做的测试,有了点新发现。解说测试之前,先说明下用flash检测大写锁定键是否开启的原理。
前提要求是:普通的input文本输入框(无论是text还是password),用户输入还是以html的input组件为基础,并非在其上加入透明的flash输入框或者其它技术平台的组件;无干扰性,用户输入没有任何其它干扰的感觉。
以此,唯一的解决办法就是js结合swf来控制。input的各种事件侦听(获得焦点、失去焦点、按键触发)都只能由js来完成,而对当前键盘状态的检测则交给flash来处理。它们之间有个很大的障碍——flash对当前键盘状态的识别必须首先获得焦点,而我们在input中输入内容的时候焦点却在input上,这成为最大的矛盾。
利用js控制页面焦点可以解决这一问题,除了webkit内核的浏览器。我们可以写出如下的伪代码:
oInput.onfocus = function() {
oSwf.focus(); //flash获得焦点
oSwf.detect(); //flash进行检测,并根据检测结果调用某个js函数
setTimeout(function() {
oInput.focus(); //input重新获得焦点,无干扰用户输入
}, 0);
}
你可能困扰为何要用setTimeout来使input重新获得焦点,这是为了让用户输入时感觉不到焦点在input和flash之间走了一圈,就好象什么事都没有发生一样。至于为什么一定要用setTimeout,那是为了模拟线程同步,确保重新获得焦点这一操作发生在最后。
就几行代码看上去很简单,实际上有几个隐蔽的信息藏在其中。在flash获得焦点并进行检测之后,input重新获得焦点,试问:这样会不会再次触发onfocus?答案是肯定的,如果不做任何处理的话,这将是个死循环;另外,input还应该侦听onblur,没道理用户在离开输入框之后还在进行提示(除非你能忍受),那么onblur这个侦听函数也会进行不断重复;更恐怖的在下面,我为每一步标上了序号,以表示它们的执行步骤:
function result() {
//1
//调用的结果函数,用以让提示区域显示或隐藏
}
oInput.onfocus = function() {
//2
oSwf.focus(); //flash获得焦点
oSwf.detect(); //flash进行检测,并根据检测结果调用结果函数
//3
setTimeout(function() {
//4
oInput.focus(); //input重新获得焦点,无干扰用户输入
}, 0);
//5
}
oInput.onblur = function() {
//6
//失去焦点后该怎么做,略
}
猜一猜这个步骤顺序是多少?你永远也答不对,因为即使在trident(ie为代表)和gecko(firefox为代表)两种内核下,它们也不相同(别提webkit了,搞定那个bug先)。
在trident中,input被用户激活后(比如说用鼠标点击),先触发focus,因此2会先执行;接下来flash获得焦点,再调用结果方法,1会被执行;继续回到onfocus方法,顺序走完3和5;onblur被触发,执行一下6;最后是setTimeout那里的4。
很奇怪的现象,因为在2时,input失去焦点而swf获得焦点,此时并没有触发onblur去执行6,反而是先执行的1;之后还有没执行完的3和5;在接下来才能轮到6。由此我们可以观察出一些trident内核的优先级顺序设计的端倪。
再来看看gecko,input被激活后,依然会先触发focus,2第一个执行;接下来也是flash获得焦点,不过在这之前input要先失去焦点,因此先触发了onblur,执行了6;然后flash进行判断,并调用结果方法,执行了1;再往后就类似了,3和5搞定后setTimeout的4最后。
探索完原理后,才有可能继续开发。我也只“刨根问底”到这个程度上,欢迎有兴趣的同学继续“往祖坟上刨”,彻底翻出来赤裸裸的现实。后续如果有新的进展,我会继续把它写出来。:)
http://army8735.org/2009/09/15/82.html
曾经考虑过用swf来检测密码输入框大写锁定键是否被打开,并且也进行了一个小实验,基本是成功的。时隔多日,最近想要把它完善一下,突然发现在chrome中根本不起作用!
一步步排查下来,终于发现在webkit内核的浏览器下,以往的使得swf获取焦点的方法根本不起作用。随之google一番,发现老外早已发现这个bug,并且目前webkit仍未解决:
http://stackoverflow.com/questions/594821/object-focus-problem-with-safari-and-chrome-browsers
这样就影响到了部分功能:比如想使用flash的检测按键功能,但它必须要求flash是在获得焦点的情况下才行。使用js可以在ie和gecko内核下轻松办到,webkit只能继续等待了。
说到对齐方式,一般联想到的无非四种:左对齐、右对齐、上对齐和下对齐。通过其中的两两组合,可以衍生出:左上角对齐、左下角对齐、右上角对齐和右下角对齐。在css中这些类型很常见,比如绝对定位:
.left_top {
position: absolute;
left: 0;
top: 0;
}
.left_bottom {
position: absolute;
left: 0;
bottom: 0;
}
.right_top {
position: absolute;
right: 0;
top: 0;
}
.right_bottom {
position: absolute;
right: 0;
bottom: 0;
}
我们很容易用css来作出四个角对齐或者四个方向居中对齐的效果。但是在as当中,则有一点点麻烦了。
说到对齐之前,首先要接触到StageScaleMode这个常量,它被赋给stage.scaleMode,用以指定整个swf程序的缩放模式,默认的是NO_SCALE,即不缩放。详细示例如下图:

当然StageScaleMode还有保持高宽比的SHOW_ALL常量以及保持高宽比并可能进行裁剪的NO_BORDER常量。上面只例举了两种对立的情形。当涉及到StageAlign的时候,我们默认为StageScaleMode就是默认的NO_SCALE,这样对齐才有意义。
在css中,也经常会遇到类似的情况。一个div区域有自己的高宽,超出区域范围之外的用overflow来控制可见度。譬如是右上角对齐的css,例子是这样:
<style>
.outer{position:relative;width:100px;height:100px;border:1px solid #f00;overflow:hidden;}
.inner{position:absolute;top:0;right:0;width:200px;height:200px;background:#000;}
</style>
<div class="outer">
<div class="inner"></div>
</div>
在as当中,想要做到同样的效果的话,可以借助StageAlign这个常量,它被赋给stage.align属性,所起的作用就和上面说的css对齐方式一样。同样是右上角对齐,我们只要在文档类中声明一下即可达到效果:
package {
import flash.display.*;
public class Main extends Sprite {
public function Main():void {
stage.align = StageAlign.TOP_RIGHT;
}
}
}
用图片做示例,页面嵌入一个原始大小为50*50的swf文件,假如外部容器(object标签或者emebed标签)的高宽值被改变,swf仍然是右上角对齐:

依此我们便可以作出自由指定显示swf内容范围的功能,比如初始swf原始大小为100*100,但是外部容器只设定宽高为50*50。当为右上角对齐时,swf只显示右上区域的50*50部分。当某件事发生时(比如鼠标点击一个按钮),外部容器款高变成100*100,如此便能看见全部的swf了。

本博客所有文章均采用