Some Thoughts on Touch-friendly Mobile Web Apps

These days I am working on a project related to English study as my startup (though part-time), due to the limited resources, I chose a native browser based solution, So that we’ll save some time for building only the single main part of the app which will run on multiple devices.

However, the app I want to build has to be user-friendly, and for an HTML based app on a mobile device, which means, relatively poorer performance, to make the user experience acceptable forms a challenge. Also, as the UX we designed, there would be some touch gestures, how to manage these gestures is also a core issue to figure out.

Luckily, both of these two issues are not tricky.

There are some transition animations during page switching, elements removing, etc. I tried to use something like margin (CSS), but it doesn’t work that well, the animation doesn’t look smooth. So I made some searches, an article says CSS 3 transform has even slower performance comparing to CSS properties like margin and the position stuffs. I believed it… But it turned to be wrong. After I randomly changed the page switching from margin to CSS 3 transform + transition, the animation runs like a charm! I can’t say it’s as good as native apps, but it’s far from acceptable.

I am using Lumia 1020 with IE 10 Mobile myself, and get a Xiaomi 2S with its built-in browser and Chrome Mobile. These browsers all perform well, especially IE 10 and Chrome Mobile. Also, I tested it on an iPhone 5 of my friend, Safari Mobile does a great job, too. That’s actually not a surprise, I wrote some web games in 2010 runs on my iPod touch 3 32G using CSS 3 animations, even with some 3D stuffs, and the game performed well.

As for gestures management, I wrote two classes respectively called GestureIdentifier and GestureDelegate and so far they works well. The idea is really simple but it really makes things much easier, hoping telling this will help some starters learn about the power of ideas which is far above code itself.

And also some quick notes:

About “meta viewport”, seems that some phones have a minimum valid width of 360 instead of 320, so any value below 360 will not work… T-T gotta make some changes to fit this new value…

About click and “tap”. Seems that if these is a click event (especially on an “a” tag), these will be a semi-transparent rectangle when you “tap”. But sometimes, it doesn’t look well… Luckily “tap” based on touch event would be a solution… As I got the two gesture classes, I can simply define a new GestureIdentifier called “tap” and write a simple rule like the path of touch has only one point. Solved.

Wishing this tool will soon be available to everyone!

[Update]

The touch event trick for preventing tap highlight works most of the time, but on IE Mobile there are still some conditions on which the semi-transparent rectangle (tap highlight) will show up, so I continued searching for solutions. Luckily, there are way better and more official ones:

For IE, there’s a meta item named “msapplication-tap-highlight”, and simply setting the content to “no” solves the problem.

<meta name="msapplication-tap-highlight" content="no" />

For Webkit (Chrome and Safari), there’s a style named “-webkit-tap-highlight-color”, and “transparent” is the answer.

html { -webkit-tap-highlight-color: transparent; }

上手 Visual Studio 2013 Preview

当然, 我最关心的还是对 JavaScript 的改进. 当然简单的看了下没法知道所有细节, 就说说我有看到的地方.

1. 在新建项目中发现了 Python 说是支持 IronPython 和 CPython, 不过想来单纯写脚本也应该没有任何问题? 不过不用 Python 表示不是很了解… 有点期待之后是否会有 NodeJS?
2. 在新建文件中看到了 LESS 和 coffeescript, 虽然也都不用,,, 囧…

JavaScript 相关:
1. 光标在变量名上时会高亮相关变量, 并且区分各种 scope, 但是怎么找也找不到重命名的选项, 不知道之后会不会提供.
2. 输入引号括号时会自动补全另一半.
3. 支持了 __defineGetter__ 等相关函数的实时运行.
4. intellisense 对象扩充了几个方法, 不过介于没有文档暂时懒得去捣鼓有什么用. 这里可以简单介绍下这个 intellisense 对象. 不出意外应该是在 VS 2012 的时候引入的, 可以让代码自己调整 IDE 的提示信息. VEJIS 0.5 使用专门开发的 intellisense 文件后能给出媲美 C# 的代码提示也是依赖于这个对象. 不过可惜知道这东西的人貌似并不多…
5. 有个 bug 貌似修复了… 之前如果打完第一个字母的时候, 提示还没有显示, 就打第二个字母, 那么就看不到提示了… 刚刚试了下这个问题不存在了.
6. 工具栏上多了个刷新按钮, 叫 Browser Link, 目测可以自动刷新网页, 但不幸的是,,, 可能是浏览器支持还没完成, 我试了下没有效果.

总的来说, VS 还是我心中前端开发的最佳选择, 虽然 2010 之后它变得有点那么丑了… 希望正式版崩给我们惊喜~

VEJIS 0.5 JavaScript 强类型编程新体验!

之前分别提到过Visual Studio 2012强大的JavaScript intellisense, 还有微软的新语言TypeScript. 测试完VS 2012 for Web增加的关于JS提示的新特性之后, 也说过要为此开发一个全新版本的VEJIS及其配套的提示文件, 现在终于算是基本搞定了. 而智能提示能够达到的程度我觉得完全可以媲美一些强类型的语言.

新版本的VEJIS相对0.4又有了不少提升, 重写了整个函数重载的代码, 使得扩展更加容易. 并且在0.4的option_(0.5改为opt_), params_的基础上进行完善. 有点小遗憾是重写的时候又把类模板忘记了, 所以后来发现要支持那东西也不简单, 可能会在下一次重写的时候实现. 不过类的话, 自从0.3开始变动就不怎么大了. 这次在0.5中则是添加了对接口(interface_)的支持, 并提供了两个新的创建特殊类型的函数nul_和delegate_.

模块(module_)部分的话, 主要是增加了对拆分一个模块到几个文件的支持, 并且丢掉了之前use_和module_连缀的写法, 以简化程序结构.

从自己写一些项目使用VEJIS 0.4的经验来看, 还是很惬意的. 以后我也会有一个更好的选择, 当然就是0.5了. 感兴趣的同学可以去VEJIS的网站看看. 现在只写了英文版, 空了之后会出中文.

http://vejis.org

关于 "动态" 模板的憧憬

大概是一年多以前还在一淘实习的时候, 萌生了这么个想法, 如果一个模板可以是基数据驱动的, 那能胜任的工作就从现在更多的格式化字符串和其他一些简单的东西变得更加复杂和强大. 当然因为是数据驱动的, 在写这样一个模板时, 也将体会到数据驱动带来的逻辑上的优势. 不过也是因为各种原因, 当时做完第一步的模板解析之后, 就没再继续了. 因为要做好, 略微困难, 而且不像原来做Prever什么的时候, 虽然代码总量大, 但可以分开成很多小块, 今天断开一点, 明天稍微回想下继续写就好.

除此之外, 另外一个数据驱动的小库, Drop(未更新), 倒是做了出来, 虽然还有诸多不完善, 到最后肯定也免不了被重写的命运… 但就实践来看, 这种方式的确是很实用的.

那回归可能会基于Drop(顺便Drop最近的版本是基于VEJIS的)之上的这个模板系统, 之前取名叫Sonne, 以后多半也是这个名字吧. 前些时候了解了下lisp语言, 才发现它和lisp之间还有一点点相似之处. 这里贴一段很久很久以前写的示例.

<!-- comments -->
{header
    <img src="xxx.jpg" />
    <h1>{title}</h1>
    <p><span>{tip}</span><span>{tip}</span>{description}</p>
}
{nav
    <ul>
    {items[]
        <li>
        {#link {
            {@hash page}
            {@value home}
            {@inner {home}}
            {@class nav-link}
        }}
        </li>
    }
    </ul>
    {#if {
        {@if {#cookies loggedin}}
        {@then <a href="#logout">log out</a>}
        {@else <a href="#login">log in</a>}
    }}
}
{content
    <div>
    {#async {
        {@type html}
        {@url content.ashx?page={#hash { {@name page} {@default home} }}}
        {@loading
            loading...
        }
        {@loaded {data}}
    }}
    </div>
}
{sidebar
    <div>
    {#async {
        {@type json}
        {@url sidebar.ashx?page={#hash { {@name page} {@default home} }}}
        {@loaded
            {data
            <ul>
                {list[]
                <li>{text}</li>
                }
            </ul>
            }
        }
    }}
    </div>
}

还是大概能看出来吧? 大括号开始, 紧接一个字母的, 是闭包, 在里面可以直接书写文本内容. #号开头的则是组件(当然, 你可以定义自己的组件), 里面的@开头的是属性, 也可以理解成参数. 包括if都是以组件的形式存在的, 所以这种一般性让Sonne具有更强的扩展性. 好吧其实, 还有个原因是我实现起来会相对容易, 不用处理更多的特殊化的语法… 😛

再以#async组件为例, 可以看到url属性对应的字符串中存在另一个组件, 所以实际上, 组件是有 “返回值” 的, 可以是字符串, 也可以是文档片段. 因为是数据驱动, 当数据发生变动的时候, #async组件所管理的文档片段也就随之变化了, 并且因为是组件, 也可以很容易地实现定时更新数据.

当时一直期望这东西能改变Web开发的某个分支, 其实现在也有这个想法, 但也不知道最后能不能做出来… 记得后来看到过某个库, 也有类似的功能, 但印象里并没有Sonne强大,,, 具体哪里没有… 忘记了…

TypeScript 小感

今天在一个Windows Phone的论坛看到微软出了个TypeScript, 瞬间想到高中时期自己构想的InviScript和Prever 2… 不过后来因为种种原因, InviScript只是停留在了构想上, 而Prever 2也在开发初期流产. 不过后来取代InviScript的, 则是VEJIS.

很高兴的是, 自己, 当然也一定是很多当时和我面临同样问题的人的设想, 最后被很好的实现. 其实Google也有一个叫Dart的语言, 不过与和InviScript的相似度赶TypeScript和InviScript的相似度就差太多了… 而且我个人也不喜欢Dart的语法, 感觉抛弃了很多JavaScript的精髓.

TypeScript和InviScript应该说都是JavaScript的超集, 并且相同的, 最终都会编译成JavaScript. 这也是我为什么取名为InviScript的原因. 意为INVIsible SCRIPT. 只在开发过程中存在.

不过同样高兴的是, 我选择了一个对于我个人而言, 更现实的实现, 即VEJIS. 虽然独立开发完InviScript也并非不可能, 但成本显然太过高昂…

VEJIS通过各种各样的函数, 从语法层面加强了JavaScript的功能, 提供了可以媲美甚至超过TypeScript的类/类型/模块工具, 当然因为VEJIS是运行时的脚本库, 代价就是一定的性能损耗. 这也限制了VEJIS的运用场景. 但就目前的实践经验来看, 在正确使用VEJIS的情况下, 这种影响是可以忽略不计的.

现在正在开发VEJIS 0.5, 重写了核心代码, 并提供了更多, 更具扩展性的功能. 甚至是delegate_, 虽然还没有决定是否会最终使用. 不论最终是中间语言形式的TypeScript更容易被接受, 还是运行时的VEJIS, 都一定程度反应了Web开发的趋势. 这点上, 我觉得Franky或许不会永远是对的. 😛

正则表达式匹配JavaScript字符串字面量

第一次遇到这个问题, 是大概两年前写代码高亮, 从当时的解决方案到现在一共有三代, 嘎嘎. 觉得还是算越来越好的.

第一代:

//那个时候自己正则还不算很精通, 也没有(?:...)这种习惯, 是以寻找结束引号为入口写出的这个正则. 思路混乱, 也存在错误.
//比如像字面量 "abc\\\"", 则会匹配为 "abc\\\", 而正确的结果应该是 "abc\\\"".
var re = /('('|.*?([^\\]'|\\\\'))|"("|.*?([^\\]"|\\\\")))/g;

第二代:

//这个匹配其实和第一代思路基本相同, 也是寻找结束引号, 通过给\\添加*解决了第一代的bug.
var re = /(['"])(?:.*?[^\\](?:\\\\)*)?\1/g;

第三代:

//老实说第三代是昨天晚上出题的时候突然想出来的(后来又修改过), 支持多行字符串字面量, 思路也有了较大的转变, 从匹配结束引号变味了匹配中间内容.
var re = /(['"])(?:\\(?:\r\n|[\s\S])|[^\\\r\n])*?\1/g

这里有一个正则里非常常用的技巧, 姑且称之为 “抢占”, 在诸如/a|./这样的正则里, 越靠前越先匹配, 通过 “抢占” 一些字符, 可以避开很多麻烦. 说起来有些抽象, 在上面这个例子里, /\\[\s\S]/就可以很自然地抢到 \” 这样的转义字符, 所以不必担心转义字符中的 ” 给匹配造成影响. 这是局部的 “抢占”, 还有更大范围的, 比如注释里的字符串或者字符串里的注释, 只要在同一个正则中, 把相关匹配都写入, 则一定是先遇到的优先. 如 /(注释正则)|(字符串正则)/g, 可以先通过这样的方式, 把内容匹配出来, 再进行进一步判断处理.

虽然可能对于多数人来说, 用处不大, 但万一呢? Best wishes~

Visual Studio 2012 for Web JavaScript自动完成体验

从Visual Studio 2005用到2010, 现在又用上了2012. 实际上之前就有体验过, 但并没有测试什么细节, 今天偶然想到, 对自己感兴趣的几个细节进行了测试, 感受是: It’s just AWESOME!

之前喜欢VS的JavaScript自动提示的原因, 是因为它会在某个环境执行你的代码, 并且用真实的结果给以智能提示, 如:

 

但VS 2010的时候, 类似的功能并不能体现到函数的实参上:

我一直很期待这个功能, 因为如果有它, 在更新自己VEJIS库的intellisense文件之后, 可以实现相当全面的自动提示. 结果, 它真的支持了!

但不仅仅是如此! 另外一个相当棒的功能就是支持异步, 比如异步加载脚本, 还有setTimeout. 先说可能大家不如我关心的setTimeout. 之后在介绍VEJIS的时候会提到前者.

除此之外, 智能提示还对数组的提示进行了优化, 会根据数组中之前出现的数据类型猜测之后出现的类型.

除了这些关于代码执行的提示, VS 2012对于代码注释的提示也有改进, 不过不是说让你写注释更方便, 而是让你写的注释被看得更方便. 并且支持多行, //和/**/都可以. 虽然之前也有<summary>等一些列的注释可以实现这样的功能, 但还是有点繁琐.

那接下来, 我就针对VEJIS谈谈对于VS 2012智能提示特性的进一步应用(这个时候在文件开头添加脚本引用了). 首先是动态加载.  在VEJIS里, require_函数用于动态加载一个脚本文件, 并且该文件会以<script>标签的形式添加到文档中. 例中动态加载的是我面向数据的一个基础库.

接下来是关于函数实参的, 像上面看到的那样, VS 2012可以根据函数被首次调用时传入的参数, 给实参加上提示, 但我们平时书写的时候, 多数时候是先写完函数再去调用. 当然, 在VEJIS下这种境况稍有不同, 因为VEJIS的函数支持重载, 也是需要指定类型的. 这样一来, 稍作修改, 就可以实现如下的功能.

同时, 因为之前说到的对于数组的提示支持, 让我还可以对VEJIS中的params类型进行处理.

又因为对于setTimeout的支持, VEJIS中类的提示也得到了加强, 虽然这个可能解释起来不是那么直观, 毕竟牵涉到其实现了.

强悍吧~ 话说如果是VEJIS老些的版本, 还支持with_, return_这些东西, 分别用于确定this和返回值的类型, 那配合上VS 2012, 就可以实现媲美强类型语言的自动提示了, 所以… 其实我已经动心开发下一个版本, 添加上这些支持. 囧. VS 2012除了这些运行时间的提示优化之外, 还增加了对很多常见对象的支持, 比如XMLHttpRequest.

顺便不得不提的是, Visual Studio 2012 for Web也有Express版, 也就意味着这么强大的工具是免费的! 各位前端同学们也加入VS阵营吧!

Adobe AIR实现支持代理的HTTP请求

也是因为那个采集项目的需要. 很失落地发现Adobe AIR的XMLHttpRequest也好, URLLoader也好, 都不支持设置代理, 一般似乎是用的系统设置. 于是自己实现了一个, 比较简单, 不支持HTTPS, 但是cookie什么的都是做到位了的. 有responseText和responseBody, 分别用于读取文本和字节文件, 如图片. 暂不支持压缩, 不过chunked是支持的.

代码已经传到github上了, 这里上一段实例, 当然, 只用到少数功能. 顺便这个是基于VEJIS, 如果不想或者不会用VEJIS可以自己稍作修改.

这里是VEJIS https://github.com/vilic/vejis
这里是这个HTTP请求 https://github.com/vilic/air-proxy-enabled-http-request-for-js

use_("http-request", function (hr) {
    var req = new hr.Request();

    //you can turn off cookies
    //req.cookieEnabled = false;
    //or turn off auto redirect
    //req.autoRedirect = false;

    req.proxy.host = "localhost";
    req.proxy.port = 1107;

    req.open("get", "http://www.vilic.info/blog/");

    //you can set request headers
    //req.setRequestHeader("Referer", "http://www.vilic.info/");

    req.send(function (req) {
        if (req.error) {
            alert(req.error);
            return;
        }

        alert(req.status);
        alert(req.responseText);
    });

    //if you use post, you'll also need to send the data
    //string and ByteArray are supported
    //req.send(data, callback);
});

Adobe AIR JavaScript跨域获取iframe内容

老实说在想到这个解决方案之前, 超级吓, 因为不解决这个问题, 就意味着项目无法实现预期的功能.

Adobe AIR中跨域的问题貌似主要是因为安全沙箱(security sandbox)不同导致的, 官方提供了两个相关的解决方案, 一个是使用iframe的sandboxRoot和documentRoot属性, 如:

<iframe
    src="http://www.example.com/local/ui.html"
    sandboxRoot="http://www.example.com/local/"
    documentRoot="app:/sandbox/">
</iframe>

我花了很长很长的时间才理解这两个属性到底指什么, 一方面是因为官方文档说得不够清楚, 我一开始一直以为app:/sandbox/是表示应用沙箱的固定用法, 一方面也是因为相关资料相对较少.

这个方法使得跨域页面中的内容(不包括iframe的src, 但包括iframe加载的内容里的各种东西, 甚至是XMLHttpRequest), 如果其URL在sandboxRoot指定的URL之下, 则并不会请求该URL在互联网上的位置, 而是请求documentRoot所指定的文件目录中的内容. 比如按上面的例子, 如果iframe加载的页面中有一个脚本的URL是http://www.example.com/local/sample.js, 则实际会加载的则是app:/sandbox/sample.js, 这里的documentRoot可以是任意目录.

但是这个方案似乎只能说是加载下文件, 并不能让脚本获取iframe的内容. 另外则有两个bridge(桥), 可以实现脚本的交互. 一个是parentSandboxBridge, 另一个是childSandboxBridge. 这两个bridge都是iframe.contentWindow的属性, 指向一个对象, 这样双方就可以通过这个对象下的属性或者方法进行信息交换了. 但需要补充的是, 不要企图传document这类的对象, 只能传传自己的(非严格界定, 但是大体意思大家应该懂). 官网上也提醒道, 如果要在iframe页面加载过程中让其中的脚本访问parentSandboxBridge, 则要在iframe中的脚本执行前就给iframe绑上parentSandboxBridge. 这时可以使用 “dominitialize” 事件.

这样一来, 跨域交互看起来终于是可行的了. 但是… 我做的是一个采集程序, 对方如果不调用我的bridge, 即使我搭好了又有什么意义呢?

后来才明白前面提到的sandboxRoot和documentRoot的意义. 对于现在多数的网页, 里面一般都会有脚本, 但那天看Google Adwords Keyword Tool的时候差点就伤心了, 从上面往下, 眼看着全是内嵌的脚本… 终于… 发现一个cues.js文件, 可能是GWT相关的信息初始化文件, 其URL是: https://adwords.google.com/cues/cues.js 于是, 将sandboxRoot设置为https://adwords.google.com/cues/, documentRoot设置为app-storage:/, 再把经过更改的cues.js文件放到app-storage:/, 这样一来, 当网页加载完成, 我安排在cues.js里的 “木马” 也就可以执行他的任务了~

如果要替换的JS文件是静态的, 那完全也可以更改后放到app:/目录(只读, 可以用trick写入, 但有兼容问题), 我放到app-storage:/是因为cues.js是动态生成的, 悲催吧…

虽然不能说是一个完美的解决方案, 但至少解决了自己的问题. 不过, 要是真遇上一个页面里没有脚本或者全是内嵌脚本的时候… 就伤了. 有人会问为什么要用那么低级的方式, 不直接发HTTP请求来解决? 老实说我也觉得蛮低级. 但Google Adwords Keyword Tool是用GWT开发的, 里面用来交换数据的格式并非常见的application/x-www-form-urlencoded, 而是text/gwt-rpc, 因为时间紧, 不能保证这是能轻松破解的(同样, 资料极少), 于是退而求其次了.