从修改系统代码到开发插件:头像开发
不久前应网友aikato的要求把头像编辑功能由flash改成了html5 (
链接)。当时没对这事有多大兴趣因为这不是个网民每次上网都要用到的功能,也许一个网民在一个网站就用一次。没想到贴出来后好几位网友表示不喜欢flash而偏好html5。网友carry0987向我介绍了他人写的头像插件和其它网站上对这个功能的修改,所以我也来尝试下将我的修改代码改成插件形式。
编码和解码
回想起来当时没有写成插件一是因为没有需要,二是由于当头像在flash编辑后上传到服务器后,服务器端的程序对上传上来的图片数据做了个解码,这说明flash程序里对图像数据做了某种编码。而html5则用了另一种称为base64的编码,所以在服务器端需要对数据做base64的解码。当时觉得如果不直接修改Discuz代码来支持这种解码而写插件的话,就要拷贝很多Discuz代码到插件里就有些无聊了。这次重新考虑了下,发现我们可以在客户端将html5生成的用base64编码的图片数据先解码,再按服务器端的flash解码方式在客户端先做对应的编码,这样上传的图片数据就和原来完全相同,因而就不用修改服务器端的代码了。
Discuz的解码程序在文件 uc_server/control/user.php 里:
function flashdata_decode($s) {
$r = '';
$l = strlen($s);
for($i=0; $i<$l; $i=$i+2) {
$k1 = ord($s[$i]) - 48;
$k1 -= $k1 > 9 ? 7 : 0;
$k2 = ord($s[$i+1]) - 48;
$k2 -= $k2 > 9 ? 7 : 0;
$r .= chr($k1 << 4 | $k2);
}
return $r;
}
这个编码基于常用的16进制数表示:0到9的数字不变,而10到15分别用字母A到F来表示。用这种0到15这16个数字和0到9加上A到F共16个字符间的一一对换,每个8位数据都可以用两个0到9加上A到F的字符组合表示,而解码就是两个0到9加上A到F的字符组合转换成一个8位数据。
举例而言:200 = 12 * 16 + 8 => (12, 8) => (C, 8) => C8。理解了这一点,我们就不难在网页代码里重现原来flash里的编码程序(因为是javascript代码,用到的函数和原来的php代码有所不同,本质是一样的):
function flashdata_encode(s) {
var r = '';
var l = s.length;
for (var i = 0; i < l; i++) {
var k = s[i].charCodeAt(0);
var k1 = k >> 4;
var k2 = k - (k1 << 4);
k1 += 48;
if (k1 > 57) k1 += 7;
k2 += 48;
if (k2 > 57) k2 += 7;
r += String.fromCharCode(k1, k2);
}
return r;
}
一些改进:
1)头像图片的三种来源:原来的代码修改旨在替换原有的头像编辑功能,所以只支持上传用户自己机器上的图片做为头像。但为什么不能象日志里添加图片那里那样,也支持用相册图片和网络图片做为头像呢?所以在这个插件里我支持了上传图片,相册图片和网络图片三种图片来源:
界面的代码参照了文件 template/default/home/editor_image_menu.htm,而处理提交选择后的图片的代码参照了文件 uc_server/control/user.php。幸好裁剪图片的代码不管图片来自何方,所以并不需要为支持相册图片和网络图片而添加很多代码。
2)以flash 为后备(fallback):以html5替代flash固然好,但万一有用户用的是旧的浏览器不支持html5怎么办呢?为此我加了一段判断用户的浏览器是否支持html5的画布功能。如果支持就用我们的html5功能,不然还是显示原来的flash功能:
if (isCanvasSupported()) {
replaceAvatarSection(document.getElementById('avatardesigner'));
...
}
//http://stackoverflow.com/questions/2745432/best-way-to-detect-that-html5-canvas-is-not-supported
function isCanvasSupported() {
var elem = document.createElement('canvas');
return !!(elem.getContext && elem.getContext('2d'));
}
3)在javascript文件里使用语言包里的汉字:在插件中的javascript文件里要使用汉字比如提示框里要显示汉字怎么办?有此问题是在javascript文件里不象在php文件和html文件能调用语言包里定义的汉字。Discuz代码自身的做法是在有些javascript文件里直接把汉字写在里面,因此对于这些文件,Discuz的每个语言版本就提供了不同版本的文件(而不是象其它文件那样通用)。而且这种做法也不适合要支持多种语言版本的插件因为把插件放到Discuz应用中心时没法提供多个语言版本的同一javascript文件。看到Discuz网站上一个讨论(
链接)给出了个很好的解决方法:在html文件里调用语言包里的汉字来定义javascript变量值,而在javascript文件里调用这些变量:
在模板文件 avatar.htm 里:
在 javascript 文件 avatar.js 里:
ctx.fillText(upload_succeed, dwidth - 140, 180);
10/17/2016补充:写了一个不基于HTML5且支持动画图片的新版本
http://www.bian-wang.com/discuz/home.php?mod=space&uid=10005&do=blog&id=1533
04/26/2017更新:网友e0759提醒本插件在Discuz2.5里不工作。检查发现由模版文件avatar.htm产生的脚本文件有语法错误。看来是因为Discuz2.5的block标签内不能插入eval标签 (
链接),解决办法是将原来开始的两句
改成
{eval $albums = getalbums($_G['uid']); }
这样就适用于2.5以上的所有版本了。
11/27/2017更新:当选择本地图片时,没有必要把图片先上传到服务器上,可以直接显示在图片元素里。这是File API提供的功能,参见
这里的讨论。注意网络图片仍有先拷贝到服务器的必要,不然在裁剪图片时会出现跨域安全错误。
11/28/2017更新:防止与其它版本的jQuery发生冲突。
11/29/2017更新:将属性里设置的load事件处理改为在代码里设置,以避免在使用Cloudflare的网站里被其更改而不工作。
插件下载:
http://www.bian-wang.com/discuz/data/userupload/10005/txgz_avatar.zip (11/29/2017最后更新)