名词:
根容器:包裹所有积木工作区/积木分类菜单/积木弹出列表的容器,即class为
injectionDiv的节点
积木工作区(或工作区):即积木可以拖放的积木代码区域
可视工作区:可以看到的工作区(不包括被积木分类菜单遮住的部分),随着滚动条的滚动,可视工作区中的内容在变化,但是可视工作区整体的宽度/高度是不变的。
内容矩形:当前角色所有工作区中的积木的边界组成的一个矩形的区域。
积木分类菜单:左侧积木分类列表的菜单
积木弹出列表:点击积木分类菜单才弹出的积木块列表,宽度固定为250
工作区坐标:工作区有一个坐标系,积木放置的位置都是在这个坐标系中。坐标系的原点在后面有描述
第一步,首先,获取内容的边界框(内容矩形):Blockly.WorkspaceSvg.getContentDimensionsExact_,内部使用Blockly.WorkspaceSvg.prototype.getBlocksBoundingBox实现
其中getBlocksBoundingBox返回容内容(所有积木)的真实的边界(没有进行缩放)。结构如下 blockBox =
{ x: boundary.topLeft.x,// 最左边的积木的x坐标 y: boundary.topLeft.y,// 最右边的积木的y坐标 width: boundary.bottomRight.x - boundary.topLeft.x, height: boundary.bottomRight.y - boundary.topLeft.y }
getContentDimensionsExact_返回其scale(具体的后面会讲解到)后的值content =
// 转换为像素。 var width = blockBox.width * scale; var height = blockBox.height * scale; var left = blockBox.x * scale; // 这块没有搞懂为什么也要*scale ??? var top = blockBox.y * scale; // 这块没有搞懂为什么也要*scale ??? return { left: left, top: top, right: left + width, bottom: top + height, width: width, height: height }
内容矩形被class为 blocklyBlockCanvas的元素包裹,内容矩形的大小也是blocklyBlockCanvas的大小。
第二步,然后,计算出可滚动的工作区的大小(包括可见和需要滚动才能看见的整个工作区),使用Blockly.WorkspaceSvg.getContentDimensionsBounded_
主要的逻辑是工作区的左边界的位置离内容区域左边界为视图(可见的工作区)宽度度的一半到一倍的大小,工作区的右边界的位置距离内容区域右边距的位置为可视工作区的宽度的一半到一倍大小:
// 在内容周围添加至少半屏宽的边框。 // 确保边框足够宽,以使块可以在整个屏幕上滚动。 // viewWidth为可视工作区(不包括积木分类菜单)的宽度,halfWidth为可视工作区宽度的一半,content是所有积木块组成的矩形内容区域 var left = Math.min(content.left - halfWidth, content.right - viewWidth); var right = Math.max(content.right + halfWidth, content.left + viewWidth); var top = Math.min(content.top - halfHeight, content.bottom - viewHeight); var bottom = Math.max(content.bottom + halfHeight, content.top + viewHeight);
工作区的上边距和下边距类似。
可以想象一下,初始化时工作区域时(没有任何内容),工作区域的宽度是2*viewWidth,高度是2*viewHeight
第三步,然后,Blockly.WorkspaceSvg.getTopLevelWorkspaceMetrics_获取整个工作工作空间调整滚动条大小所需要的所有值,
var metrics = { contentHeight: contentDimensions.height, // 工作区的高度(可以滚动查看完整的工作区) contentWidth: contentDimensions.width, // 工作区宽度 contentTop: contentDimensions.top, // 工作区上边界相对原点的y轴坐标 contentLeft: contentDimensions.left, // 工作区左边界相对原点x轴的坐标 viewHeight: svgSize.height, // 可视工作区的高度,(可以看到的积木工作区,不包括积木分类菜单), viewWidth: svgSize.width, // 可视工作区的宽度 viewTop: -this.scrollY, // 当前可视工作区的上边缘与父对象(根容器)的偏移量(右边界对齐方式),是垂直滚动条滚动的垂直距离的相反数 viewLeft: -this.scrollX, // 当前可视工作区左边缘与父对象(根容器)的偏移量(右边界对齐方式),是水平滚动条滚动的水平距离的相反数 absoluteTop: absoluteTop, // 视图顶部边缘占位(不属于可视工作区),不能放置积木 absoluteLeft: absoluteLeft, // 视图的左边缘占位(不属于可视工作区),不能放置积木(积木分类菜单在左侧时,就是积木分类菜单栏的宽度) toolboxWidth: toolboxDimensions.width,// 积木分类菜单栏的高度 toolboxHeight: toolboxDimensions.height,// 积木分类菜单栏的宽度 flyoutWidth: flyoutDimensions.width,// 积木弹出列表的宽度(如果积木弹出列表展开的话),否则为0 flyoutHeight: flyoutDimensions.height,// 积木弹出列表的高度(如果积木弹出列表展开的话),否则为0 toolboxPosition: this.toolboxPosition // 积木分类菜单的Top, bottom, left or right. }
第四步,然后,通过Blockly.WorkspaceSvg.setTopLevelWorkspaceMetrics_将工作空间转换成坐标,即将blocklyBlockCanvas元素(内容矩形)设置transform如 transform="translate(875,635) scale(0.675)"的过程
其中比率为ratio = this.handlePosition_ / this.scrollViewSize_,即:比率=滚动条手柄的偏移量/滚动条容器的长度(和(ws.viewLeft - ws.contentLeft) / ws.contentWidth 的值一致),
然后计算出当前滚动条对应的的工作区域在x或y轴上的真实偏移量,以x的偏移量为例
var metrics = this.getMetrics(); // 获取的相关工作空间的信息,详情查看第三步 // xyRatio.x即是在x轴上的比率,值为ratio if (goog.isNumber(xyRatio.x)) { // 以x轴为例,x轴往右滚动,整个空间就是整体往左移,所以是-metrics.contentWidth * xyRatio.x,而工作区域一开始相对原点的位置是contentLeft,要减去 // scrollX这个偏移量是真实的用户在滚动滚动条时,工作区域滚动了多少px this.scrollX = -metrics.contentWidth * xyRatio.x - metrics.contentLeft; }
次时translate的x,y值也就出来了
var x = this.scrollX + metrics.absoluteLeft; // 工作空间相对于原点的x轴的偏移量 = x轴上的滚动的偏移量 + 可视工作区中不可用的部分(积木分类菜单栏的宽度) var y = this.scrollY + metrics.absoluteTop; this.translate(x, y);
想象一下初始化时(内容矩形没有积木块),当scrollX为0,scrollY为0时,可视区域的偏移量应该是translate(120, 0),即当x滚动条在最右侧,y滚动条在最底部的时候,内容矩形blocklyBlockCanvas的位置也在translate(120,0)。坐标系的原点(0,0)在当前内容矩形(当前内容矩形width=height=0)的位置。当然随着滚动条的滚动,原点位置就会随着改变。
-metrics.contentLeft = metrics.contentWidth * xyRatio.x,在初始的时候(没有积木块),如果viewWidth为620,则contentWidth为1240,contentLeft为 为-metrics.contentLeft * 2 = metrics.contentWidth,即ratio = 1/2,次时x轴滚动条应该在最右侧,滚动条宽度为可视工作区的一半,偏移也是可视工作区的一半。 默认没有内容的时候,translate位置 translate(120,0) scale(0.675)
现在我们来看scale时怎么来的。原来时外部注入blockly时配置进来的
Blocks.defaultOptions = { zoom: { controls: true, wheel: true, startScale: 0.675 }, ... }
Blockly.Options函数将调用Blockly.inject传入的配置解析后编程自己的配置。
第五步,设置滚动条,水平和垂直设置滚动条的方式类似,不过函数是分开的。以水平设置滚动条为例,this.scrollViewSize_为滚动条背景的长,滚动条滚动都在这个容器范围内。Blockly.Scrollbar.prototype.resizeContentHorizontal函数中的处理是,滚动条背景和工作区的比例ratio = this.scrollViewSize_ / a.contentWidth,使用工作区的偏移*ratio就计算处理滚动条的真实像素偏移
var handlePosition = (hostMetrics.viewLeft - hostMetrics.contentLeft) *
this.ratio_;
this.handlePosition_将滚动条的真实像素偏移保存下来。横向滚动条的x为0时,可视工作区的左边界和工作区的左边界重合。纵向滚动条的y为0时,可视工作区的上边界与工作区的上边界重合.
第六步,当载入积木块时,重走第一到第五步,重绘工作区。第一次走第一到第五步时,工作区是没有内容的,此时滚动条等数据都是比较容易确定的:
第四步已经知道,当内容矩形没有积木的时候,坐标原点(0,0)即是内容矩形blocklyBlockCanvas的位置translate(120, 0)。一旦加载积木,积木形成的内容矩形填充进工作区域,那么根据第一步,工作区的四边都有可能新增空间,所要做的是当前可视工作区不变,滚动条调整到对应的位置即可。当滚动条滚动时,视觉上时内容矩形中的内容都随之变换位置,实际上就是内容矩形blocklyBlockCanvas的translate值在变化(内容矩形中的积木也有一个 translate值,这个值是相对于坐标系的值,在放入内容矩形时就确定,除非二次移动这块积木。)。
当插入了积木到内容矩形时(scratch-gui触发调用ScratchBlocks.Xml.clearWorkspaceAndLoadFromXml),调用Blockly.Xml.domToWorkspace会把积木添加到工作区,设置积木在工作区坐标的位置。积木添加完成以后会调用workspace.setResizesEnabled(true);重新适配整个可视区域相对于根容器的位置(重走第一到第五步)。
如果觉得本文不错,请点击右下方【推荐】!