《TypeScript图形渲染实战:2D架构设计与实现》 —3.3.6 正确的_viewportToCanvasCoord
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所示。
图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基础上的,后续章节会介绍如何实现精灵系统。
图3.16 _viewportToCanvasCoordinate终极效果图
- 点赞
- 收藏
- 关注作者
评论(0)