Category Archives: Develop - Page 2

[Cocoa学习] 限制 NSTextField 中输入文本的长度

需求

NSTextField 作为一个常用的输入控件,有些在使用时需要去限制其中可以输入文本的长度,而 NSTextField 本身并没有提供这个功能,这就需要我们自己去想办法来实现了。

方法

在网上找了一下,发现可以通过继承 NSFormatter 实现一个子类来实现这个功能,参考了一下找到的代码[1],再将主要的 isPartialStringValid 方法根据自己的需要改了一下。

 
- (BOOL)isPartialStringValid:(NSString **)partialStringPtr
       proposedSelectedRange:(NSRangePointer)proposedSelRangePtr
              originalString:(NSString *)origString
       originalSelectedRange:(NSRange)origSelRange
            errorDescription:(NSString **)error 
{
    int size = [*partialStringPtr length];
    if ( size > maxLength ) {
        if (origSelRange.location == [origString length]) {
            // 如果修改的位置在原来字符串的最后,则不做修改,只是拒绝内容修改
        } else {
            // 如果修改的位置在原来字符串的中间,就根据剩余的可用的长度把新增加的字符串进行截取
            int preLen = origSelRange.location + (maxLength - [origString length]) + origSelRange.length;
            *partialStringPtr = [NSString stringWithFormat:@"%@%@",
                                 [*partialStringPtr substringToIndex:preLen],
                                 [origString substringFromIndex:origSelRange.location+origSelRange.length]];
 
            (*proposedSelRangePtr).location = preLen;
        }
 
        return NO;
    }
    return YES;
}

参考资料

在 REALbasic 中注册 AppleEvent

之前为了注册一个自定义协议,需要通过注册 AppleEvent 来实现,在 Objective-C 中,可以很方便的使用 NSAppleEventManager 来注册 AppleEvent 句柄,但是在 REALbaisc 中,是没有办法直接去调用 NSAppleEventManager 的,所以需要通过声明然后调用 C API 来实现相应的功能。

与 NSAppleEventManager 中功能相对应的 C API 有 AEInstallEventHandler, NewAEEventHandlerUPP 等,通过这些 API 我们也可以在 REALbasic 中来注册 AppleEvent 了,再配合 Info.plist 中的 URLScheme 声明,即可实现 URL 自定义协议处理句柄。

#if TargetCarbon
    soft declare function AEInstallEventHandler Lib CarbonLib ( _
    theAEEventClass as Integer, _
    theAEEventID as Integer, _
    handler as Integer, _
    handlerRefcon as Integer, _
    isSysHandler as Boolean) as Integer
 
    Soft Declare Function NewAEEventHandlerUPP Lib CarbonLib (userRoutine as Ptr) as Integer
 
    Static CallbackUPP as Integer = 0
    If CallbackUPP = 0 then
      dim m as MemoryBlock =  AddressOf ForwardCarbonAEEventToObject
      If m is nil then
        Return
      End if
      CallbackUPP = NewAEEventHandlerUPP(m)
    End if
 
    dim v as Variant = me
 
    dim OSError as Integer = AEInstallEventHandler( _
    OSTypeToUInt(kInternetEventClass), _
    OSTypeToUInt(kAEGetURL), _
    CallbackUPP, _
    v.Hash, false)
 
    msgbox str(OSError)
#endif

先使用 NewAEEventHandlerUPP 来生成一个 AppleEvent 回调函数的句柄,然后调用 AEInstallEventHandler 来注册一个共享函数 ForwardCarbonAEEventToObject 为 AppleEvent 事件处理句柄。

AEInstallEventHandler 所需的 AEEventClass 和 AEEventID 都是一个 4 字节的整型,但是通常我们在调用的时候,是用的一个 4 字符的字符串,因此需要一个函数来将 4 字符转换为 4 字节的整形。

// code from ToolbarSearchField by The ZAZ Studios
// http://www.thezaz.com/opensource/realbasic/macosx/searchfield/
static m as new MemoryBlock(4)
m.LittleEndian = false
m.StringValue(0, 4) = s
return m.UInt32Value(0)

在 ForwardCartonAEEventToObject 里,参数 theEvent 和 replyEvent 都量个整形,为了从这两个参数里拿到数据,还需要使用 AEGetParamPtr 来从 AppleEvent 中拿到数据。

soft declare function AEGetParamPtr lib CarbonLib ( _
    theAppleEvent as Integer, theAEKeyword as Integer, _
    desiredType as Integer, byref actualType as Integer, _
    dataPtr as Ptr, maximumSize as Integer, _
    byref actualSize as Integer) as Integer

当然还有一系列的 AEGetDataDesc、AEGetDescSize 等函数可以,具体可以查 Xocde 随带的库文档。

关于注册自定义协议,可以参考这篇文章

通过 Core Foundation 中的一些 C API,在 REALbasic 也可以完成一些平台相关的工作,虽然麻烦了些:)

慎用 script 节点的 src 属性来传递参数

在有些使用 javascript 来渲染数据的时候,为了能动态获取不同的数据,并且保持 javascript 代码的可扩展性,会将 javascript 代码中获取数据的部分需要的参数提取出来,做为参数放在 script 节点的外部。

一般来说,传递参数到 javascript 文件内部的方法有两种,一种是将参数写在一个 script 节点中,写成全局变量的方式的传递给紧接着这个 script 节点的外部 javascript 中,Google Analytics 就是使用这样的方式:

<script type="text/javascript">
var p1 = "v1", p2 = "v2";
</script>
<script type="text/javascript" src="foo.js"></script>

另外一种是将参数直接写在 script 节点的 src 属性中,相当于一个页面的查询字符串一样:

<script type="text/javascript" src="foo.js?p1=v1&p2=v2"></script>

不过,使用 script 节点的 src 属性来传递参数需要注意一个很重要的问题,那就是动态变化的 src 属性会导致缓存失效

现在,为了网站性能的需要,一般都会将 javascript 文件放在独立的服务器上,并设置一个较长的过期时间,这样客户端只会在第一次访问网站时需要去下载这个 javascript 文件。但是,如果使用 src 来传递参数,就可能会使这种缓存策略失效。特别是 src 中存在动态参数的情况,例如统计脚本中如果有一个 ip 参数,那么访客每次连上线时,可能 ip 都会不同,就会导致 javascript 缓存失效了。

解决这个问题的方法也很简单,简单地的将 src 属性中的参数放到 script 节点的一个自定义属性中就可以了,例如 data-args,而 src 属性只需要保留一个时间戳就可以了。因为使用 src 属性来传递参数本来就需要定位 script 节点,所以改由 data-args 自定义属性来传递参数并不会增加额外的代码。只不过页面会通不过 w3c 的验证罢了 :)

&lt;script type="text/javascript" src="foo.js" data-args="p1=v1&amp;p2=v2"&gt;&lt;/script&gt;

再次提醒,慎用 script 节点的 src 属性来传递参数 :)

REALbasic 中使用结构体作为 Win32 API 的参数及使用 Win32 API 停止服务

在之前,我使用 ShellExecute 这个 API 来执行命令(《REALbasic 中使用 ShellExecute 执行命令》),然后通过这个方法来停止某个服务,但是今天想加服务运行状态检测,这样就可以在服务没有运行的情况下不再询问用户是否需要停止某个服务。

为了省事,我一开始决定同样使用 cmd 去执行一个命令,将服务状态输出到一个临时文件中,再通过读取这个临时文件,查找特征字符串来判断服务是否运行:

// 伪代码
ShellExecute("cmd.exe /c sc query service_name &gt; tmpfile")
dim serviceStatus as string
serviceStatus = TextInputStream.Open(tmpfile).ReadAll()
if instr(serviceStatus, "STOPPED") &lt; 1 then
  // 提示用户是否停止服务
end if

但是这样做不太靠谱,因为 sc 这个命令执行是要时间的,而 ShellExecute 是异步的,这就导致了在调用 ShellExecute 执行完 sc query 之后,临时文件 tmpfile 里并不是马上就有服务状态的内容了。

为了防止这个情况,我再加了一个临时文件内容的检测,如果为空的话,sleep 100ms,再继续读取,如果超过 10 次仍没有内容,直接当作服务正在运行来对待。

// 伪代码
ShellExecute("cmd.exe /c sc query service_name &gt; tmpfile")
dim serviceStatus as string
dim tryCount as integer
while tryCount &lt; 10 and serviceStatus = ""
  tryCount = tryCount + 1
  App.SleepCurrentThread(100)
  serviceStatus = TextInputStream.Open(tmpfile).ReadAll()
 
wend
if instr(serviceStatus, "STOPPED") &lt; 1 then
  // 提示用户是否停止服务
end if

但是这样仍然不能百分百保证正确,于是想到可不可以继续用 win32 api 来做这些,Google 了一下,找到一篇博客[1],看了之后发现如果只需要停止服务的话,还是蛮简单的,决定使用 win32 api 来实现我想要的功能了。

在停止服务这个过程中需要用到的 win32 api 有:OpenSCManagerW、OpenServiceW、QueryServiceStatus、ControlService,还有一个结构体:SERVICE_STATUS。

win32 api 的参数都很好对应,数值、句柄类型的参数直接使用 Integer 即可,字符串类型使用 CString,而 SERVICE_STATUS 这个结构体也可以直接通过工程菜单添加。

在传参的时候需要注意两点:

  1. 如果是字符串类型的参数并且调用的接口是 Unicode 类型的,那么需要在传入参数之前,将参数值转换为 Unicode 编码。可以通过 ConvertEncoding(text, Encodings.UTF16) 来转换。
  2. 如果参数是结构体,那么参数前的传参方式就要写 ByRef,也就是按引用传值,字符串和数值类型的参数可以不写或者写 ByVal。

以下是 4 个 win32 api 的 REALbasic 定义:

soft declare function OpenSCManagerW Lib "advapi32.dll" _
(ByVal lpMachineName As CString, ByVal lpDatabaseName As CString, _
ByVal dwDesiredAccess As Integer) As Integer
 
soft declare function OpenServiceW lib "advapi32.dll" _
(ByVal hSCManager As Integer, ByVal lpServiceName As CString, _
ByVal dwDesiredAccess As Integer) As Integer
 
soft declare function QueryServiceStatus lib "advapi32.dll" _
(byval hService as integer, byref lpServiceStatus as SERVICE_STATUS) _
as boolean
 
soft declare function ControlService lib "advapi32.dll" _
(byval hService as integer, byval dwControl as integer, _
byref lpServiceStatus as SERVICE_STATUS) as boolean

有了 api 定义之后,就可以直接根据上面提到的 vc++ 流程来停止某个服务了。

完整的服务操作 api 可以参阅微软的 MSDN。

这样看来,REALbasic 要写出支持 Windows 7 特性的应用程序也不是难事:)

参数资料

  1. VC++启动和停止服务, huangchonghai
  2. QueryServiceStatus, MSDN

关于 REALbasic 中使用 AutoDiscovery 时发生错误 40 的问题

今天在使用 AutoDiscovery 发送数据时,发生了错误码为 40 的错误,直接在 UDPSocket 的属性列表里找了一下,没有找到对应的错误码。

在网上搜了一下,找到这篇帖子,里面提到在发送大量数据时,UDPSocket 就会报错误,并且这个错误没有在文档中提到,因此这应该是一个系统级别的错误。

MonkeybreadSoftware 提到了 unix 的错误码定义:

#define    EMSGSIZE    40     /* Message too long */

联想到在程序中是在今天开始有问题的,而且今天添加了一大堆数据,看来的确是同于 UDP 消息过大造成的错误 40。

在网上找了找,使用 UDP 发送消息时,报文的大小最好不要超过 MTU,否则会就容易丢包。我在测试时是使用的 127.0.0.1,包大小为 12.5K,照理说应该是直接走 loopback 而不需要走路由的,就算超过 MTU 也应该能发送,不清楚是不是因为 OS 内部实现机制的问题。

为了彻底解决这个问题,最后还是采用了 TCP 来传递大量数据,只使用 UDP 来传递一些控制信息。

在 AIR 中突破同源策略访问 iframe 中的内容

本篇所讲关于 AIR 的内容是基本 HTML+JS,而不是 Flex 或其他。 在 AIR 中,如果当前页面有一个 iframe,并且我们需要获取这个 iframe 的内容,但是如果 iframe 里包含的是远程页面,在默认情况下,浏览器策略并不允许这种情况,这就是同源策略的限制。 在 Titanium 这个类似于 AIR 的平台中,默认情况下是可以直接从应用的页面中去读取 iframe 中的内容而没有任何限制,但是在 AIR 中不可以。在网上搜索了一番,再看了好几遍 AIR 的文档之后,终于找到解决问题的方法了。 在 AIR 中,iframe 标签拥有额外的几个属性:sandboxRoot、documentRoot、allowCrossDomainXHR 及 ondominitialize。这里为了解决读取 iframe 内容所用到的属性就是 sandboxRoot 及 documentRoot。 通过 sandboxRoot 及 documentRoot,可以将本地的页面映射为远程页面,并在沙盒中运行,另外,在沙盒中运行的页面就会拥有映射域名的域。例如,我们可以将本地的 proxy.html 映射为 http://example.com/local/proxy.html,这样,在实际运行时,proxy.html 中如果用 document.domain 获取页面所在的域,就会得到 example.com,这个时候如果在 proxy.html 中添加一个 iframe,这个 iframe 指向我们实际需要获取内容的地址,因为域相同,就可以直接获取 iframe 的内容了。 例如,我们要在 AIR 中获取 http://example.com/member/login.html 的内容,或者操作这个页面的元素,就可以先用一个 proxy.html,用来实现将本地文件运行在沙盒中,再用一个 login.html 来对实际网页进行操作。
<!– proxy.html –> <iframe src=”http://example.com/local/login.html” id=”frame” width=”100%” sandboxRoot=”http://example.com/local/” documentRoot=”app:/” name=”frame” height=”100%”></iframe>
    <!– login.html –> <iframe id=’f' frameborder=”0″ scrolling=”no” src=”http://example.com/member/login.html” width=”100%” height=”100%”></iframe> <script type=”text/javascript”> alert(document.domain); var f = document.getElementById(‘f’); f.onload = function() { alert(f.contentWindow.document.documentElement.innerHTML); }; </script>
    在 proxy.html 中,虽然 iframe 的 src 属性是 http://example.com/locale/login.html,但是由于这个 iframe 具有 sandboxRoot 属性,所以实际请求会被重定向到相对于 sandboxRoot,并且使用 documentRoot 进行定位的实际地址 app://login.html,但是这个时候 login.html 中具有  document.domain=”example.com” 这个属性,所有可以直接使用 iframe.contentWindow 来访问 iframe 中的内容。 虽然解决方法有些麻烦,但终归可以实现突破同源策略的限制来读取 iframe 的内容了。 当然,如果你有更好的方法,请不吝赐教:)

    使用 arguments.caller 实现自动回调

    这是一篇介绍类似于 js hack 的文章,只是说明了一种可行的途径,并且可能会增加代码复杂程度,具体项目中是否可以使用还请自辨:) 在前端开发过程中,有许多业务流程可能是需要用户进行登录的,并且登录过程是放在弹出层中,这样就可以不用刷新页面,增强用户体验。在登录时,用户的操作就会被打断,为了进一步增强用户体验,我们可能需要在登录完成后自动继续进行用户在登录前想进行的操作。 假设有这样一个场景,用户需要发表一个留言,但是发表留言是需要登录的,而发表留言的输入框是一直显示的,这也就要求在用户点击了发表按钮时对用户登录状态进行验证,传统的做法是将在用户登录状态检查封装在一个函数之中,这个函数接收一个回调参数,如果登录验证通过,则执行回调函数。这样的逻辑可以用以下代码表示:
    function doAction() { checkLogin(function() { // 处理业务逻辑 }); } function checkLogin(callback) { if (isLogin) { callback(); } else { showLogin(callback); } } function showLogin(callback) { document.getElementById(“login-btn”).onclick = function() { isLogin = true; callback(); }; }
      总觉得这样的方式会将业务逻辑放到一个匿名函数中,而不是放在了按钮的事件响应函数中,感觉上不是那么好。这对整个事件响应函数的改动比较大,主要的业务逻辑都放在了登录验证函数的回调中。而希望可以是下面这样:
      function doAction() { // 直接在函数开始进行登录验证,将业务逻辑独立出来 if (!doLogin()) { return; } // 处理业务逻辑 } function doLogin() { if (isLogin) { return true; } else { var callback = function() {}; // 自动组装 callback showLogin(callback); return false; } } function showLogin(callback) { document.getElementById(“login-btn”).onclick = function() { isLogin = true; callback(); }; }
        这里的关键在于怎么“自动组装 callback”,很幸运的是,JavaScript 的 Function 提供了一个属性 caller 可以用来获取调用当前函数的函数是什么。并且还可以用 caller 的 arguments 属性来获取 caller 执行时的参数是什么,这也就使我们自动组装 callback 并恢复 caller 的原参数成为可能。

        <div id=”result”></div>
        <input type=”button” value=”Sumit” onclick=”sendPost()” id=”post” />
        <input type=”button” value=”Clear login” onclick=”isLogin=false” id=”reset” />
        <div id=’login’ style=”border:1px solid #000;padding:10px;margin:10px;display:none”>
        <strong>Login</strong><br />
        <input type=”button” id=’loginbtn’ value=”Login” />
        </div>
        <script type=”text/javascript”>
        // 是否登录
        var isLogin = false;
        // 检查登录状态
        var checkLogin = function(cfg) {
        if (isLogin) {
        return true;
        }
        cfg = cfg || {};
        var callback = null;
        // 自动执行回调
        if (cfg.autoCallback && arguments.callee.caller) {
        try {
        var caller  = arguments.callee.caller,
        args    = caller.arguments || [],
        scope   = cfg.callbackScope || {},
        newArgs = [];
        for (var i = 0; i < args.length; ++i) {
        newArgs.push(args[i]);
        }
        callback = caller ? function() {
        try {
        caller && caller.apply(scope, newArgs);
        } catch (e) {
        //alert(e.message);
        }
        } : null;
        } catch (e) {
        callback = null;
        }
        }
        showLogin(callback);
        return false;
        };
        function sendPost() {
        if (!checkLogin({autoCallback:true})) {
        return;
        }
        document.getElementById(‘result’).innerHTML += ‘Hello<br />’;
        }
        function showLogin(callback) {
        document.getElementById(‘login’).style.display = ‘;
        document.getElementById(‘loginbtn’).onclick = function() {
        document.getElementById(‘login’).style.display = ‘none’;
        isLogin = true;
        callback && callback();
        }
        }
        </script>
        其实弄出这么个方法来实现登录自动回调也是为了方便写代码,不用每次去将一堆业务逻辑放到 callback 中,直接在函数开始回一个 if (!checkLogin({autoCallback:true})) 就行。 caller 属性在 MDC 中被标记为“非标准”的,但是在主流浏览器中,都是支持这个属性的,也就是说,在浏览器兼容性上使用 caller 是没有问题的。 不过,使用奇技淫巧容易伤身体,慎用:)

        REALbasic 中使用 ShellExecute 执行命令

        在 REALbasic 中,如果需要执行 cmd 命令,可以直接使用 Shell 类,但是这样的话,编译成 Windows 程序时会额外需要一个 Shell.dll 的动态链接库,这对于我这样的 1exe 爱好者是不能忍受的。但是对于 Mac OS X 和 Linux 的生成目标来说,是不存在这个问题的,因为 Mac OS X 的应用程序本身就是一个文件夹,而 Linux 的目标不会生成额外的链接库。因此,需要针对 Windows 进行特殊处理。于是在网上搜索解决方案,找到了 VB 中执行程序的几种方法:

        1. 使用 CreateProcess

        通过 CreateProcess 以及使用管道,可以执行外部程序并获取输出,但是这个方法过于烦琐,并且我也不需要外部进程执行完毕后的输出结果,因此不采用。

        2. 使用 Shell 方法

        VB 里有一个 Shell 方法,但是在 RB 中并没有,所以此路不通。

        3. 使用 ShellExecute

        这个方法同样是一个系统 API,可以直接通过 RB 的 declare 声明并调用它,在测试之后,使用 declare 来使用系统 API 不会生成额外的 dll,正是我需要的。

        首先在 RB 的某个模块中添加一个方法 ShellExecute,用来封装对系统 API 的请求:

        function ShellExecute(hWnd as Integer, lpOperation as String, _ lpFile as String, lpParameters as String, lpDirectory as String, nShowCmd as Integer)

        注意,在 RB 中添加方法时,参数列表中的 _ 需要去掉,这里是为了排版的需要而加上的。

        这个 API 是定义在 shell32.dll 中的,在 ShellExecute 方法中先需要声明:

        soft declare function ShellExecuteA Lib ”shell32.dll” _ (ByVal hWnd As Integer, ByVal lpOperation As CString, _ ByVal lpFile As CString, ByVal lpParameters As CString, _ ByVal lpDirectory As CString, ByVal nShowCmd As Integer) As Integer soft declare function ShellExecuteW Lib ”shell32.dll” _ (ByVal hWnd As Integer, ByVal lpOperation As CString, _ ByVal lpFile As CString, ByVal lpParameters As CString, _ ByVal lpDirectory As CString, ByVal nShowCmd As Integer) As Integer

        然后来调用这个 API:

          dim ret as Integer
            Try
              ret = ShellExecuteW(hWnd, ConvertEncoding(lpOperation, Encodings.UTF16), _
              ConvertEncoding(lpFile, Encodings.UTF16), _
              ConvertEncoding(lpParameters, Encodings.UTF16), _
              ConvertEncoding(lpDirectory, Encodings.UTF16), nShowCmd)
            Catch
              ret = ShellExecuteA(hWnd, lpOperation, lpFile, lpParameters, lpDirectory, nShowCmd)
            End
            return ret

        把 ShellExecuteW 放在 Try … Catch 中是为了兼容的需要,因为像 Windows 98 这样比较老的系统中,系统内部编码还不是 Unicode,还没有 ShellExecuteW 这样结尾是 W 的 API。

        另外,因为 RB 内部是使用的 UTF-8 编码,而系统 API ShellExecuteW 使用的编码是 Unicode,所以参数在传递前需要进行转码,将字符串参数值转换为 Unicode 编码,否则在执行一个明明正确的命令时,会出现“找不到文件”的错误。

        好了,这样就可以使用 ShellExecute 方法来执行外部程序了,例如:

          #if TargetWin32
            dim ret as Integer
            ret = Win32Helper.ShellExecute(hWnd, "open", "cmd.exe", _
            "/c shutdown -t 10", "", 0)
          #endif

        上面的代码就调用 cmd.exe 执行了命令 shutdown -s -t 10,也就是 10 秒后关机。

        其实这个方法也可以用来打开文档或者网址,具体的用法可以去 MSDN 找一找。

        当然,如果不在乎生成的 Windows 目标还有额外的 dll 的话,完全可以使用 RB 自带的 Shell 类,功能也强上不少。

        HTML5 Canvas 起步(3) – 颜色与渐变

        上一篇介绍了 HTML5 中 Canvas 的路径,这篇将要介绍一下 Canvas 中的颜色及渐变。

        Canvas 中的基本颜色系统

        在 Canvas 中,颜色主要用途就是在绘制路径时,用来指定填充颜色和边框颜色。

        Canvas 中的颜色参数值有两种格式:

        1. 如果透明度为 1.0,也就是不透明,颜色值的格式就与一般使用一样,为:#AABBCC,其中 AA、BB、CC 分别为 Red、Green、Blue 分量。

        2. 如果透明度不为 1.0,也就是带透明,颜色值格式可以使用 rgba(r, g, b, a),其中 r、g、b、a 分别为 Red、Green、Blue 分量和透明度。透明度的值为 0 至 1.0 之间的一个数值。

        3. 颜色值还可以为已知的颜色名称,例如 red、blue、green 等。

        总的说来,Canvas 中颜色值的格式与 CSS 中一致,因此颜色值没有特别需要注意的地方。

        图片附件

        <div id="_mcePaste">&lt;canvas id="canvas" width="600" height="500"&gt;&lt;/canvas&gt;
        &lt;script type="text/javascript"&gt;
            var canvas = document.getElementById("canvas");
            var ctx = canvas.getContext("2d");
            // fill a rectangle whit sepecified fill style
            function fillRect(color) {
                ctx.fillStyle = color;
                ctx.fillRect(0, 0, 150, 100);
                ctx.font = "10pt Arial";
                ctx.fillStyle = "white";
                ctx.fillText(color, 6, 20);
                ctx.translate(160, 0);
            }
            ctx.translate(0, 50);
            // fill rect with color name
            fillRect("green");
            // fill rect with 0xAABBCC
            fillRect("#AABBCC");
            // fill rect with rgba(50, 100, 150, 0.5)
            ctx.translate(0, -25);
            fillRect("rgba(50,100,150,0.5)");
         
            // fill another rect with rgba(150, 100, 50, 0.5)
            ctx.translate(-110, 50);
            fillRect("rgba(150, 100, 50, 0.5)");
        &lt;/script&gt;</div>

        注意,以上代码需要使用 Firefox 3.5 来查看,在 Firefox 3.0.x 中,Canvas 的 Context 对象不支持 fillText 方法,而我安装的 Chrome 2.0.174.0 对 translate 方法的实现有误。

        2. Canvas 中的渐变

        WHATWG 的 Canvas 规范中规划了两种渐变模式,一种是线性渐变,另一种是径向渐变。如果需要在 Canvas 中使用渐变,首先要根据你所要创建的渐变模式来调用 Context 的相应方法来创建一个渐变对象,这个对象就是用来控制渐变的效果。

        Read more »

        HTML5 Canvas 起步(2) – 路径

        上一篇介绍了 HTML5 中 Canvas 的基本概念,这篇将要介绍一下 Canvas 中的基本图形。

        图形的基础 - 路径

        在 Canvas 中,所有基本图形都是以路径为基础的,也就是说,我们在调用 2dContext 的 lineTo、rect 等方法时,其实就是往已经的 context 路径集合中再添加一些路径点,在最后使用 fill 或 stroke 方法进行绘制时,都是依据这些路径点来进行填充或画线。

        在每次开始绘制路径前,都应该使用 context.beginPath() 方法来告诉 Context 对象开始绘制一个新的路径,否则接下来绘制的路径会与之前绘制的路径叠加,在填充或画边框时就会出现问题。在绘制完成路径后,可以直接使用 context.closePath() 方法来关闭路径,或者手动关闭路径。另外,如果在填充时路径没有关闭,那么 Context 会自动调用 closePath 方法将路径关闭。

        基本路径方法

        1. beginPath, closePath

        这两个方法在前面已经介绍过,分别用来通知 Context 开始一个新的路径和关闭当前的路径。

        在 Canvas 中使用路径时,应该要保持一个良好的习惯,每次开始绘制路径前都要调用一次 beginPath 方法,否则画出来的效果难看不说,还会严重影响性能。

        在下面这张图中,左边的图形在每次绘制矩形前都调用了一次 beginPath 来清除之前的路径并重新开始绘制新的路径,而后面的图形则就只在绘制所有图形前调用了一次 beginPath 来清除路径,因此,虽然这里是使用的边框色是 #666,但是右边的图形颜色比左边的深一些,因为每次使用 stroke 绘制边框时,会把之前的路径再次绘制一遍,叠加起来颜色就比原来深一些。

        图片附件

        <div id="_mcePaste">&lt;canvas id="canvas" width="500" height="500"&gt;&lt;/canvas&gt;
        &lt;script type="text/javascript"&gt;
            var canvas = document.getElementById("canvas");
            var ctx = canvas.getContext("2d");
            ctx.strokeStyle = "#666";
            function useBeginPath() {
                for (var i = 0; i &lt; 5; ++i) {
                    ctx.beginPath();
                    ctx.rect(10 + i*20, 10 + i*20, 210 - i*40, 210 - i*40);
                    ctx.stroke();
                }
            }
            function notUseBeginPath() {
                ctx.beginPath();
                for (var i = 0; i &lt; 5; ++i) {
                    ctx.rect(240 + i*20, 10 + i*20, 210 - i*40, 210 - i*40);
                    ctx.stroke();
                }
            }
            useBeginPath();
            notUseBeginPath();
        &lt;/script&gt;</div>

        在 Context 中路径数较少时,如果不考虑显示效果,性能上还可以接受,但是如果 Context 中的路径数很多时,在开始绘制新路径前不使用 beginPath 的话,因为每次绘制都要将之前的路径重新绘制一遍,这时性能会以指数下降。

        因此,除非有特殊需要,每次开始绘制路径前都要调用 beginPath 来开始新路径。 Read more »

        .