引入Flash那些事

最近做的事和Flash打交道比较多,简单来说,就是要在第三方的页面引入一个Flash的广告,播放一会,让用户能够关闭。

起初总觉得,就引入一个Flash嘛,Adobe都给出了官方的使用方法,直接拿来用不就成了。顶多IE下不支持object元素的appendChild,大不了拼接字符串然后通过innerHTML来创建就好了。

但是真正做的时候,开始测试各个浏览器的时候,才发现情况远没有想的这么简单……

关于如何引入Flash

这大概是最基本的问题,其他的一切问题都是基于Flash可以正确引入到DOM中这个前提的。

最先的方式自然是使用adobe给出的官方方式,即<object><embed /></object>的结构来嵌入一个Flash。根据各种资料,<embed>标签用于Firefox,而<object>标签用于IE,两者合在一起,就是一相相对完美的方式。

但是事情绝对不会就这样完结,这个时候号称最先进最标准的Chrome浏览器过来插了一脚:当使用<object><embed /></object>结构来嵌入Flash时,在Chrome中会出现Flash不显示的问题。

关于这个问题,他必须有2个前提:

  • 使用<object><embed /></object>的结构引入Flash。
  • <object>前,有一个元素有background-image。

当以上两个条件都满足时,如果打开这个页面,特别是在本地打开,有一定机率会看不到Flash。但是Flash确实存在,甚至连Flash的声音都可以听到,仅仅是浏览器没有将其绘制在屏幕之上。

由于该问题与网络环境有着联系,在本地特别容易重现,找一个不大的但有播放声音的.swf文件,然后HTML代码如下:

<!DOCTYPE html>  
<html>  
<head>  
    <meta charset="utf-8">
    <title>flash消失问题</title>
</head>  
<body>  
    <div style="background-image: url('baidu_logo.gif'); width: 270px; height: 129px;"></div>
    <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" type="application/x-shockwave-flash" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,0,0" width="600" height="360">
        <param name="allowScriptAccess" value="always">
        <param name="quality" value="high">
        <param name="wmode" value="transparent">
        <param name="movie" value="foo.swf">
        <embed wmode="transparent" src="foo.swf" quality="high" allowscriptaccess="always" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" width="600" height="360">
    </object>
</body>  
</html>  

将代码保存为一个.html文件,并保存其相关联的Flash文件,在本地打开,刷新几次,应该就能看到这个问题了。

为了这个问题,最后决定推翻<object><embed /></object>的DOM结构。因为<object><embed>两者是独立的,因此就在各浏览器之间测试了标签的兼容性,得出的结果是<embed>标准可以被现有的几乎所有浏览器支持,包括了IE、Firefox、Chrome和Opera。最后,也在这篇文章上对引入Flash的方式有了最终的确认,仅使用<embed>标签足够引入Flash。而在去除<object>标签后,Chrome下的诡异问题也随之消失了。

新版本的Chrome似乎已经没有了这个问题,大可不必在意。

关于如何移除Flash

又是一个看上去很简单的问题,最简单地代码自然是:

var element = document.getElementById('someFlash');  
element.parentNode.removeChild(element);  

看上去非常简洁、有效,理论上几乎不会出现问题的代码,却在Opera这小众分子上倒下了。简单来说,如果<embed>标签带有position: fixed;样式,使用以上的代码移除该元素,Opera不会对界面进行重新绘制,这导致Flash会在屏幕上留下一个“残影”,保留被移除时播放的那一帧的画面,只有在系统有重新绘制的行为(包括滚动、切换其他窗口等)时,才会消失。

可复现的代码如下:

<!DOCTYPE html>  
<html>  
<head>  
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

    <meta charset="utf-8">
    <title>flash移除的问题</title>
</head>  
<body>  
    <embed id="removeMe" style="position: fixed; right: 0pt;" wmode="transparent" src="foo.swf" quality="high" allowscriptaccess="always" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" width="600" height="360">
    <span onclick="tryRemoveFlash();" style="border: 1px solid rgb(45, 147, 248); padding: 2px 6px; background-color: rgb(255, 255, 204);">点击移除Flash</span>
    <script>
        function tryRemoveFlash() {
            var element = document.getElementById('removeMe');
            element.parentNode.removeChild(element);
        }
    </script>
</body>  
</html>  

通过使用Opera浏览器访问来查看这个问题。

对于这个问题,解决办法也不难,在移除<embed>标签之前,先将该标签隐藏起来,并要求浏览器进行一次绘制,将“隐藏”的效果体现出来,随后再移除即可:

var element = document.getEleemntById('someFlash');  
element.style.display = 'none'; // 先隐藏起来  
// 利用setTimeout,让DOM有一次重绘的时间
setTimeout(  
    function() {
        element.parentNode.removeChild(element);
    }, 
    0
);

关于如何给Flash加链接

给图片加链接很简单,只需要外面套一层<a>元素即可,在HTML5中<a>元素的内容终于被定义为Flow Content,这使得<a>元素内部可以放置大部分元素。

这里有个错误,事实上在HTML5中<a>元素被定义为Transparent Element,即其内容模型由其父元素决定,而非单纯的Flow Content。

但是如果在<a>元素里放<embed>元素呢?很遗憾,Flash作为外部插件,非常暴力地将<a>给灭了,链接完全起来到任何效果。

那么,就只能创建一个<a>元素,让他浮动在Flash之上。但是这里就必须保证<a>元素和<embed>的大小相同,以免链接干扰到其他的元素。

对于除IE6以外的浏览器,都支持使用top / bottom以及left / right成对出现的样式来定义其高度和宽度,因此解决该问题还是比较方便的,只需要一个容器即可:

<div style="position: relative;">  
    <a href="xxx" style="position: absolute; left: 0; right: 0; top: 0; bottom: 0;"></a>
    <embed ... />
</div>  

但是还剩下IE6,对于IE6,因为<a>元素是绝对定位的,因此height: 100%;也是没有效果的。在这里有2种方法来解决这个问题:

  • 使用css expression,相当消耗资源,但是大小的确定十分精准。
  • 保证外部容器有确切的高度,并使用overflow: hidden;的样式,配合<a>元素一个非常大的height值来实现全容器的点击监控。

鉴于css expression那人人避之唯恐不及的效率问题,最后选择了后者:

<div style="position: relative; width: 300px; height: 300px;">  
    <a href="xxx" style="position: absolute; top: 0; width: 100%; height: 2000px;"></a>
    <embed ... />
</div>  

这个方案在其他浏览器中同样也是有效的。

另外,在部分浏览器(实测Opera 11和IE9)下,如果<a>元素没有背景色的话,是无法点击的,对于这一点,Snandy的博文里有很好的总结,解决方案是给<a>加上一个背景色,再将透明度设为0即可:

.cover-link {
    background-color: #fff; opacity: 0; filter: alpha(opcaity=0);
}

最后,看到仅仅简单地使用一个Flash,也存在这么多的问题,认为只要Flash存在就不需要推广HTML5的开发者们,是不是还能坚持自己的想法呢?