《TypeScript图形渲染实战:2D架构设计与实现》 —3.3.6 正确的_viewportToCanvasCoord

举报
华章计算机 发表于 2019/12/12 19:35:34 2019/12/12
【摘要】 本节书摘来自华章计算机《TypeScript图形渲染实战:2D架构设计与实现》 一书中第3章,第3.3.6节,作者是步磊峰。

3.3.6  正确的_viewportToCanvasCoordinate方法实现

  通过上节的测试知道,_viewportToCanvasCoordinate的实现没有考虑border和padding设置后的情况,那么本节就来修正这个问题。为了更好地显示效果,先设置一个完整的盒模型,如下CSS代码所示。

 

/*

 css选择器:# 表示id选择器

*/

#canvas {

    background : #ffffff ;           /* canvas背景为白色 */

    margin : 10px ;                    /* 将canvas的margin设置为0px */

    border : 80px solid red ; /* 如果没有solid,则boarder无效,设置border

    为红色 */

    padding : 20px ;                   /* 设置padding 为 20个像素 */

}

 

  然后来看一下修正_viewportToCanvasCoordinate方法后的正确效果,如图3.15所示。

 image.png

图3.15  修正_viewportToCanvasCoordinate方法后的正确效果图

  参考图3.15会发现,即使有80px红色的border,以及20px白色的padding,鼠标点选也不受影响,正确选中鼠标单击处的节点(或精灵)。那么来看一下实现过程。具体代码如下:

 

// 将鼠标事件发生时鼠标指针的位置变换为相对当前canvas元素的偏移表示

// 这是一个私有方法,意味着只能在本类中使用,子类和其他类都无法调用本方法

// 只要是鼠标事件(down / up / move / drag .....)都需要调用本方法

// 将相对于浏览器viewport表示的点变换到相对于canvas表示的点

private _viewportToCanvasCoordinate ( evt : MouseEvent ) : vec2 {

    if ( this . canvas ) {

        // 切记,很重要一点:getBoundingClientRect ( )方法返回的ClientRect

        let rect : ClientRect = this . canvas . getBoundingClientRect ( ) ;

           

        // 作为测试,每次mousedown时,打印出当前canvas的boundClientRect的位置

        和尺寸

       if ( evt . type === "mousedown" ) {

            console . log (" boundingClientRect : " + JSON . stringify ( rect ) ) ;

            // 测试使用输出相关信息,打印出MouseEvent的clientX / clientY 属性

            console . log ( " clientX : " + evt . clientX + " clientY : " +

            evt.clientY ) ;

        }

           

        // 获取触发鼠标事件的target元素,这里总是HTMLCanvasElement

        if ( evt . target )

        {

            let borderLeftWidth : number = 0 ;                                                                                                 //返回border左侧离margin的宽度

            let borderTopWidth : number = 0 ;                                                                                                //返回border上侧离margin的宽度

            let paddingLeft : number = 0 ;                                                                                                          //返回padding相对border(border存在的话)左偏移

            let paddingTop : number = 0 ;                                                                                                   //返回padding相对border(border存在的话)上偏移

            // 调用getComputedStyle方法,这个方法比较有用

            let decl : CSSStyleDeclaration  = window . getComputedStyle

            ( evt . target as HTMLElement ) ;

                

            // 需要注意,CSSStyleDeclaration中的数值都是字符串表示,而且有可能

            返回null

            // 所以需要进行null值判断

            // 并且返回的坐标都是以像素表示的,所以是整数类型

            // 使用parseInt转换为十进制整数表示

            let strNumber : string | null =  decl . borderLeftWidth ;

            if ( strNumber !== null ) {

                borderLeftWidth  = parseInt ( strNumber , 10 ) ;

            }

            if ( strNumber !== null ) {

                borderTopWidth = parseInt ( strNumber , 10 ) ;

            }

            strNumber = decl . paddingLeft ;

            if ( strNumber !== null ) {

                paddingLeft = parseInt(strNumber,10);

            }

            strNumber = decl . paddingTop ;

            if ( strNumber !== null ) {

                paddingTop = parseInt ( strNumber , 10) ;

            }

               

            // a = evt . clientX - rect . left,将鼠标点从viewport坐标系变换

            到border坐标系

            // b = a – borderLeftWidth,将border坐标系变换到padding坐标系

            // x = b – paddingLeft,将padding坐标系变换到context坐标系,也就

            是canvas元素坐标系

            let x : number  = evt . clientX - rect . left - borderLeftWidth

            - paddingLeft ;

            let y : number  = evt . clientY - rect . top - borderTopWidth -

            paddingTop ;

            // 变成向量表示

            let pos : vec2 = vec2 . create ( x , y ) ;   

               

            // 测试使用输出相关信息

            if ( evt . type === "mousedown" ) {

                console . log ( " borderLeftWidth : " + borderLeftWidth + "

                borderTopWidth : " + borderTopWidth ) ;

                console . log ( " paddingLeft : " + paddingLeft + " paddingTop :

                " + paddingTop ) ;

                console . log ( " 变换后的canvasPosition : " + pos .

                toString( ) ) ;

            }

            return pos ;

        }

         // 对于错误,直接报错

         alert (" canvas为null " ) ;

         throw new Error ( " canvas为null " ) ;

    }

       

    // 对于错误,直接报错

    alert ( " evt . target为null " ) ;

    throw new Error ( " evt . target为null " ) ;

}

 

  关于关键的坐标转换算法,在上面的代码中有详细注释。除了图3.15的效果外,再看看border及padding更加通用设置时的效果,CSS代码如下设置:

 

 /*

    css选择器:# 表示id选择器

*/

#canvas {

   background : #ffffff ; /* 背景白色 */

   margin : 10px 15px 20px 25px ; /* 将canvas的margin设置为4个数值 */

         

   border-style : solid ;

   border-width : 25px 35px 20px 30px ; /* top - right - bottom - left 顺序 */

   border-color : gray ;

   /* 使用各种单位表示padding */

   padding-top : 10px ;      

   padding-righ t: 0.25em ;

   padding-bottom : 2ex ;

   padding-left : 20% ;

   -webkit-box-shadow : 4px 4px 8px rgba ( 0 , 0 , 0 , 0.5 ) ;

   -moz-box-shadow : 4px 4px 8px rgba ( 0 , 0 , 0 , 0.5 ) ;

   box-shadow : 4px 4px 8px rgba ( 0 , 0 , 0 , 0.5 ) ;

}

 

  运行后的效果如图3.16所示。

  根据图3.16所示,会看到更新后的_viewportToCanvasCoordinate能够自适应盒模型的各种参数,精确地进行鼠标点选的碰撞检测,并且即使节点(或精灵)发生位移、缩放或旋转等仿射变换,也能精确地进行点选。而这些变换及碰撞检测,都是建立在_viewportToCanvasCoordinate基础上的,后续章节会介绍如何实现精灵系统。

 image.png

图3.16  _viewportToCanvasCoordinate终极效果图


【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。