Skip to content

获取包围盒 ​

当我们用鼠标绘制图形,或对元素进行对齐、矩形碰撞检测的时候,需要获取元素实际内容的位置和大小(包围盒),用来进行下一步操作。

包围盒模型 ​

图中是一个元素由内到外形成的不同类型包围盒,每个包围盒都包含位置(x、y)和大小(width、height)信息,类似 CSS 的盒模型。

盒子模型

margin 包围盒 ​

外部边界: 基准边界 + margin。

render 包围盒 ​

渲染边界: 笔触边界 + 阴影等。

stroke 包围盒 ​

笔触边界: 基准边界 + stroke,可响应交互事件的边界。

box 包围盒 ​

基准边界: 包含 padding,以此为基准向内、向外延伸边界。

content 包围盒 ​

内容边界: 填充内容的边界,不包含 padding,一般用于测量 Text 的实际文本大小。

OBB 和 AABB ​

当元素旋转后,在 不同的坐标系下 会形成不同的 OBB 和 AABB 包围盒。

包围盒

示例 ​

我们通过 创建图形 的例子,来了解 包围盒与坐标系 的相互作用 ​

按下鼠标拖动开始画矩形,抬起结束,当缩放平移视图后,仍然可以准确绘制新的矩形。

ts
// #图形编辑器 [创建图形 - 进入绘制模式]
import { App, DragEvent, Rect } from 'leafer-ui'
import '@leafer-in/editor' // 导入图形编辑器插件
import '@leafer-in/viewport' // 导入视口插件 (可选)


const app = new App({ view: window, editor: {}, fill: '#333' })

app.tree.add({ tag: 'Text', x: 100, y: 100, text: '2秒后进入绘制模式,按下鼠标拖动可创建矩形,10 秒后再回到正常模式', fill: '#999', fontSize: 16 })


app.tree.add(Rect.one({ editable: true, fill: '#FEB027', cornerRadius: [20, 0, 0, 20] }, 100, 300))
app.tree.add(Rect.one({ editable: true, fill: '#FFE04B', rotation: 10, cornerRadius: [0, 20, 20, 0] }, 300, 300))

app.editor.select(app.tree.children[2])

setTimeout(() => {

    // 2秒后进入绘制模式
    app.mode = 'draw'

    // 创建矩形(拖拽)
    let rect: Rect

    const events = [
        app.on_(DragEvent.START, () => {
            rect = new Rect({ editable: true, fill: '#32cd79' })
            app.tree.add(rect)
        }),

        app.on_(DragEvent.DRAG, (e: DragEvent) => {
            if (rect) rect.set(e.getPageBounds()) // 获取事件在 page 坐标系中绘制形成的包围盒
        })]


    setTimeout(() => {

        app.off_(events)

        // 10 秒后回到正常模式
        app.mode = 'normal'

    }, 10000)

}, 2000)
js
// #图形编辑器 [创建图形 - 进入绘制模式]
import { App, DragEvent, Rect } from 'leafer-ui'
import '@leafer-in/editor' // 导入图形编辑器插件
import '@leafer-in/viewport' // 导入视口插件 (可选)


const app = new App({ view: window, editor: {}, fill: '#333' })

app.tree.add({ tag: 'Text', x: 100, y: 100, text: '2秒后进入绘制模式,按下鼠标拖动可创建矩形,10 秒后再回到正常模式', fill: '#999', fontSize: 16 })


app.tree.add(Rect.one({ editable: true, fill: '#FEB027', cornerRadius: [20, 0, 0, 20] }, 100, 300))
app.tree.add(Rect.one({ editable: true, fill: '#FFE04B', rotation: 10, cornerRadius: [0, 20, 20, 0] }, 300, 300))

app.editor.select(app.tree.children[2])

setTimeout(() => {

    // 2秒后进入绘制模式
    app.mode = 'draw'

    // 创建矩形(拖拽)
    let rect

    const events = [
        app.on_(DragEvent.START, () => {
            rect = new Rect({ editable: true, fill: '#32cd79' })
            app.tree.add(rect)
        }),

        app.on_(DragEvent.DRAG, (e) => {
            if (rect) rect.set(e.getPageBounds()) // 获取事件在 page 坐标系中绘制形成的包围盒
        })]


    setTimeout(() => {

        app.off_(events)

        // 10 秒后回到正常模式
        app.mode = 'normal'

    }, 10000)

}, 2000)

检测元素的包围盒是否碰撞 ​

ts
// #元素包围盒 [检测 rect2 是否与 rect1 碰撞]
import { Leafer, Frame, Rect, DragEvent, Bounds } from 'leafer-ui'

const leafer = new Leafer({ view: window, fill: '#333' })

const rect1 = Rect.one({ fill: '#FEB027', draggable: true }, 100, 100)

leafer.add(Frame.one({ children: [rect1] }, 100, 100, 500, 600)) // rect1 在 frame 内

const rect2 = Rect.one({ fill: '#FFE04B', draggable: true }, 200, 50)  // rect2 在 frame 外

leafer.add(rect2)

// 检测 rect2 是否与 rect1 碰撞 (通过世界坐标中的 box 包围盒跨层级检测)
rect2.on(DragEvent.DRAG, () => {

    const rect2Bounds = new Bounds(rect2.worldBoxBounds)  
    rect1.stroke = rect2Bounds.hit(rect1.worldBoxBounds) ? 'blue' : '' // 碰撞则显示蓝色边框

})

获取方法 ​

事件中的获取方法 ​

DragEvent

元素上的获取方法 ​

名称描述
boxBounds元素在 内部坐标系 中的基础边界(OBB 包围盒)
renderBounds元素在 内部坐标系 中的渲染边界(AABB 包围盒)
worldBoxBounds元素在 世界坐标系 中的基础边界(AABB 包围盒)
worldRenderBounds元素在 世界坐标系 中的渲染边界(AABB 包围盒)
getBounds()获取 AABB 包围盒(边界)
getLayoutBounds()获取 OBB 包围盒(边界),含缩放、旋转等布局属性
getLayoutPoints()获取 OBB 包围盒(边界)的四个坐标点)

数学计算 ​

Bounds 类 ​

下一步 ​

局部渲染 ​

Released under the MIT License.