本文重点是实现preserveAspectRatio
中<meetOrSlice>
参数的效果,也是background-size
中值为cover
,contain
属性效果。在实现以前,简单介绍一下SVG中的viewport
, viewBox
和preserveAspectRatio
的关系。javascript
当咱们建立SVG标签时,其实是建立了一个隐形的无限延伸的画布。css
画布是无限延伸的,可是人的视野是有限的,咱们须要设置一个固定的区域,而后在这个固定的区域去绘制图形,使其可见。因此咱们经过给SVG设置width
和height
属性(或者经过css设置宽高)设置一个可见区域,这个区域就是viewport,也就是视窗。这个区域是初始坐标系。html
在不作任何坐标系转换 (transform) 的状况下,咱们绘制一个SVG图形,实际是绘制在画布上,可是是以viewport的坐标系为参考坐标系绘制的。能够理解为画布是绘制实体,视窗是用来肯定具体绘制的位置和尺寸的。html5
什么是viewBox呢?java
能够理解为咱们手里有一个任意尺寸的方形框,即viewBox,这个框能够在视窗的区域内任意位置游走。咱们经过设置viewBox的属性值,能够指定这个框的尺寸(width, height),以及具体游走在视窗的那个位置(x, y)。git
在框内的画面就是咱们最终看到的画面,咱们将框内的区域“裁剪”出来,而后经过缩放填充整个视窗。而具体的缩放规则就要看preserveAspectRatio属性设置的值。github
咱们经过viewBox裁剪了一个区域,而后将这个区域缩放填充整个视窗。为了便于理解,咱们将经过viewBox裁剪出来的区域称为content,将视窗称为box。算法
为了使content在缩放过程不变形,咱们须要保持content的宽高比,若是content和box的宽高比相等,则content经过缩放能够恰好填满整个box。可是若是宽高比不相等,那应该如何填充?bash
preserveAspectRatio就是为了解决这个问题而产生的。preserveAspectRatio中有两个参数<align>
和<meetOrSlice>
,一个值决定content按照什么规则缩放,一个值决定缩放后的content与box的对齐方式。svg
其中<meetOrSlice>
属性值的效果和background-size
中cover
和contain
相似。
关于更详细的SVG坐标系的解释,参考这两篇文章:
理解SVG坐标系和变换:视窗,viewBox和preserveAspectRatio
接下来就详细介绍一下<meetOrSlice>
是如何控制填充效果的。
为了行文方便,仍然用content和box分别代指用于填充的矩形图和被填充的盒子。由于本文重点在于探索和实现preserveAspectRatio的<meetOrSlice>
效果,在这里就不对<align>
参数展开介绍了。
<meetOrSlice>
参数有两个值:meet
和slice
,其中meet
相似于background-size
中的contain
,slice
相似于background-size
中的cover
。
在MDN preserveAspectRatio对这两个属性值是这样描述的:
- meet (默认值) 图形将缩放到:
- 宽高比将会被保留
- 整个SVG的viewbox在视图范围内是可见的
- 尽量的放大SVG的viewbox,同时仍然知足其余的条件。
在这种状况下,若是图形的宽高比和视图窗口不匹配,则某些视图将会超出viewbox范围(即SVG的viewbox视图将会比可视窗口小)。
- slice 图形将缩放到:
- 宽高比将会被保留
- 整个视图窗口将覆盖viewbox
- SVG的viewbox属性将会被尽量的缩小,可是仍然符合其余标准。
在这种状况下,若是SVG的viewbox宽高比与可视区域不匹配,则viewbox的某些区域将会延伸到视图窗口外部(即SVG的viewbox将会比可视窗口大)。
简单归纳就是两句话,在保持content宽高比不变的状况下:
meet / contain
:缩放,使box包含(contain)完整的content,box内部可能产生空白。重点:不产生“越界”现象
slice / cover
:缩放,使content覆盖(cover)box所有区域,content可能会超出box区域。重点:不产生空白区域
在了解了这个两个属性值的含义以后,咱们再来探究这两个值的具体计算规则。box和content都是矩形,咱们将矩形分为三类:正方形、竖向矩形、横向矩形:
为了更精确的对比全部尺寸获得的结果,咱们分别设置三种类型的box尺寸,以及三种类型的content尺寸,以下:
在SVG中,设置meet
和slice
,获得的结果以下(SVG缩放中会影响到矩形的边框宽度,因此一样的边框宽度由于缩放比例不一样会致使最终的视觉宽度不一样):
根据定义和实验结果,咱们能够发现:
meet
模式下,为了使 content最大程度的被完整包含在box内部,老是content的长边与box对应边对齐。
slice
模式下,为了使box被填满,老是content的短边与box对应边对齐。
但这两点并不能包含所有状况,咱们看👇例子:
从上面的4个例子中能够发现,当box和content都是同向的矩形(均为横向或者竖向)时,meet
状况下,还须要继续判断短边的长度;而在slice
的状况下还须要继续判断长边的长度。
因此咱们能够这样描述:
meet
:老是比较二者的长边:
长边同边(同边:都是横向或者竖向矩形):假设都为横向矩形,根据宽高比计算在同一个宽度下二者的高度,判断哪一个的高度更高(反之同理):
contentHeight > boxHeight
(此时box更“扁”):为了使content被完整包含在box里,须要让content的高等于box的高(对齐高)boxHeight > contentHeight
(此时content更“扁”):使content的宽等于box的宽(对齐宽)长边异边(异边:一个为横向另外一个为竖向):假设content为横向,长边为宽,box为竖向,长边为高(能够理解为content更扁一点),则使content的宽等于box的宽(对齐宽),反之同理。
slice
:老是比较二者的短边:
contentHeight > boxHeight
(此时box更“扁”) :使content的宽等于box的宽(对齐宽)boxHeight > contentHeight
(此时content更“扁”):使content的高等于box的高(对齐高)如今,咱们已经知道meet
和slice
的缩放规律,根据图,咱们能够进一步概括逻辑。在这里,根据矩形形状的特色,使用“扁”做为统一标准:同宽状况下,高度越小,越扁。
在保持content宽高比缩放的状况下,比较content和box的“扁”度:
meet
slice
伪代码能够这样描述:
if(type == 'meet'){
if(box_扁 > content_扁){
content_高 * scale = box_高;
content_宽 * scale = new_content_宽;
}else{
content_宽 * scale = box_宽;
content_高 * scale = new_content_高;
}
}else if(type == 'slice'){
if(box_扁 > content_扁){
content_宽 * scale = box_宽;
content_高 * scale = new_content_高;
}else{
content_高 * scale = box_高;
content_宽 * scale = new_content_宽;
}
}
复制代码
根据伪代码的描述,为了真正实现这个算法,咱们须要:
获得一个矩形的“扁”度
获得scale值
其实上面两点很是容易获取:
若是一个矩形越扁,意味着宽高比越大,因此能够经过宽高比 width / height
来获取“扁”度。
而scale,在伪代码中其实已经能发现scale的计算方法(假设对齐宽):
contentW * scale = boxW;
scale = boxW / contentW;
因此缩放后的content宽高为:
newContentW = scale * contentW;
newContentH = scale * contentH;
复制代码
由此,咱们就能够写出meet
和slice
方法了:
function meetOrSlice(type, boxW, boxH, contentW, contentH){
let boxRadio = boxW / boxH,
contentRadio = contentW / contentH,
scaleW = (boxW / contentW) || 1,
scaleH = (boxH / contentH) || 1,
scale = 1;
if(type == 'meet'){
scale = boxRadio >= contentRadio ? scaleH : scaleW;
}else if(type == 'slice'){
scale = boxRadio >= contentRadio ? scaleW : scaleH;
}
return {
w: scale * contentW,
h: scale * contentH
}
}
复制代码
最后和SVG对比一下效果,其中关于边框的两个问题:
(0,0)
时,会有一半的边框超出svg的窗口范围,致使被截断。完结,撒花🎉。