Jupyter小部件的布局#

本笔记本展示了如何布局 Jupyter 交互式小部件,以构建丰富且具有 响应性 的基于小部件的应用程序。

layout 属性#

Jupyter 交互式小部件具有一个 layout 属性,它暴露了一些影响小部件布局的 CSS 属性。

暴露的 CSS 属性#

以下属性映射到同名 CSS 属性的值(下划线被替换为短横线),应用于相应小部件的顶级 DOM 元素。

尺寸#

  • height

  • width

  • max_height

  • max_width

  • min_height

  • min_width

显示#

  • visibility

  • display

  • overflow

盒模型#

  • border

  • margin

  • padding

定位#

  • top

  • left

  • bottom

  • right

弹性盒子#

  • order

  • flex_flow

  • align_items

  • flex

  • align_self

  • align_content

  • justify_content

网格布局#

  • grid_auto_columns

  • grid_auto_flow

  • grid_auto_rows

  • grid_gap

  • grid_template

  • grid_row

  • grid_column

简写 CSS 属性#

您可能已经注意到,某些 CSS 属性如 margin-[top/right/bottom/left] 似乎缺失了。对于 padding-[top/right/bottom/left] 等也是如此。

实际上,您可以通过单独的 margin 属性原子性地指定 [top/right/bottom/left] 边距,通过传递字符串 '100px 150px 100px 80px' 来分别设置 toprightbottomleft 边距为 10015010080 像素。

同样,flex 属性可以包含 flex-growflex-shrinkflex-basis 的值。border 属性是 border-widthborder-style (必需)border-color 的简写属性。

简单示例#

以下示例展示了如何调整 Button 的大小,使其视图的高度为 80px,宽度为可用空间的 50%

from ipywidgets import Button, Layout

b = Button(description='(50% width, 80px height) button',
           layout=Layout(width='50%', height='80px'))
b

layout 属性可以在多个小部件之间共享,并直接分配。

Button(description='Another button with the same layout', layout=b.layout)

描述#

您可能已经注意到,长描述被截断了。这是因为描述长度默认是固定的。

from ipywidgets import IntSlider

IntSlider(description='A too long description')

您可以更改描述的长度以适应描述文本。然而,这将使小部件本身变得更短。您可以通过使用小部件的样式调整描述宽度和小部件宽度来同时更改这两者。

style = {'description_width': 'initial'}
IntSlider(description='A too long description', style=style)

如果您需要更大的灵活性来布局小部件和描述,您可以直接使用标签小部件。

from ipywidgets import HBox, Label

HBox([Label('A too long description'), IntSlider()])

自然尺寸,以及使用 HBox 和 VBox 的排列#

大多数核心小部件都有默认的高度和宽度,可以很好地拼接在一起。这使得基于 HBoxVBox 辅助函数的简单布局能够自然对齐:

from ipywidgets import Button, HBox, VBox

words = ['correct', 'horse', 'battery', 'staple']
items = [Button(description=w) for w in words]
left_box = VBox([items[0], items[1]])
right_box = VBox([items[2], items[3]])
HBox([left_box, right_box])

LaTeX#

像滑块和文本输入这样的小部件具有可以渲染 LaTeX 方程的描述属性。Label 小部件也可以渲染 LaTeX 方程。

from ipywidgets import IntSlider, Label
IntSlider(description=r'\(\int_0^t f\)')
Label(value=r'\(e=mc^2\)')

数字格式化#

滑块有一个读数区域,可以使用 Python 的 格式说明小型语言 进行格式化。如果读数区域的空间对于滑块值的字符串表示来说太窄,将应用不同的样式以显示并非所有数字都可见。

弹性盒子布局#

上述的HBoxVBox类是Box小部件的特殊案例。

Box小部件实现了完整的CSS弹性盒子规范以及网格布局规范,使得在 Jupyter 笔记本中可以实现丰富的响应式布局。它旨在提供一种高效的方式,用于在容器中对项目进行布局、对齐和分配空间。

再次强调,整个弹性盒子规范可以通过容器小部件(Box)的layout属性以及包含的项目来暴露。可以在所有包含的项目之间共享相同的layout属性。

致谢#

以下关于弹性盒子布局的教程遵循了Chris Coyier的文章《A Complete Guide to Flexbox》 的思路,并且在获得了许可的情况下,使用了该文章的文本和各种图片。

基本概念和术语#

由于flexbox是一个完整的模块,而不仅仅是一个属性,它涉及到很多方面,包括它的一整套属性。其中一些属性是设置在容器(父元素,被称为”flex容器”)上的,而其他的则是设置在子元素(被称为”flex项目”)上的。

如果常规布局是基于块和内联流动方向的,那么flex布局则是基于”flex流动方向”。请参考规范中的这张图,解释了flex布局背后的主要思想。

Flexbox

基本上,项目将按照”主轴”(从main-start到main-end)或”交叉轴”(从cross-start到cross-end)进行布局。

  • “主轴” - flex容器的主轴是沿其排列flex项目的主要轴。请注意,它不一定是水平的;它取决于flex-direction属性(见下文)。

  • “main-start | main-end” - flex项目从容器的main-start开始放置,向main-end方向延伸。

  • “主轴尺寸” - flex项目的宽度或高度(取决于主轴维度)是项目的主轴尺寸。flex项目的主轴尺寸属性是’width’或’height’属性,取决于主轴维度。 交叉轴 - 与主轴垂直的轴称为交叉轴。其方向取决于主轴方向。

  • “cross-start | cross-end” - flex线用项目填充,并从flex容器的cross-start一侧开始放置,向cross-end一侧延伸。

  • “交叉尺寸” - flex项目的宽度或高度(取决于交叉维度)是项目的交叉尺寸。交叉尺寸属性是’width’或’height’,取决于交叉维度。

父元素的属性#

Container

display#

display可以是flexinline-flex。这定义了一个flex容器(块级或内联)。

flex-flow#

flex-flowflex-directionflex-wrap属性的简写,它们共同定义了flex容器的主轴和交叉轴。默认值是row nowrap

  • flex-direction (column-reverse | column | row | row-reverse )

    这建立了主轴,从而定义了flex项目在flex容器中的放置方向。Flexbox是一个(除了可选的包裹外)单方向布局概念。可以将flex项目视为主要是水平行或垂直列排列。 Direction

  • flex-wrap (nowrap | wrap | wrap-reverse)

    默认情况下,flex项目都会尝试适应一行。你可以使用这个属性改变这一点,允许项目根据需要进行换行。方向在这里也起作用,决定了新行堆叠的方向。 Wrap

justify-content#

justify-content可以是flex-startflex-endcenterspace-betweenspace-around之一。这定义了沿着主轴的对齐方式。它有助于分配剩余的空闲空间,当一行上的所有flex项目都是不可变的,或者它们是灵活的但已达到最大大小时。当项目溢出该行时,它也会对项目的对齐产生一些控制。 Justify

align-items#

align-items可以是flex-startflex-endcenterbaselinestretch之一。这定义了在当前行上沿着交叉轴布局flex项目的默认行为。可以将其视为交叉轴(与主轴垂直)版本的justify-content。 Items

align-content#

align-content可以是flex-startflex-endcenterbaselinestretch之一。当交叉轴有多余的空间时,它会对flex容器的行进行对齐,类似于justify-content如何沿着主轴对齐单个项目。 Items

注意:当只有一行flex项目时,此属性无效。

项目的属性#

Item

如果父元素不是flexbox容器(即其display属性等于flex或inline-flex),则项目的与flexbox相关的CSS属性不会有任何影响。

order#

默认情况下,flex项目按源顺序布局。然而,order属性控制它们在flex容器中出现的顺序。

<img alt="Order" style="width: 500px;"/>

flex#

flex是三个属性的简写,分别是flex-growflex-shrinkflex-basis。第二个和第三个参数(flex-shrinkflex-basis)是可选的。默认值是0 1 auto

  • flex-grow

    这定义了flex项目在必要时增长的能力。它接受一个无单位的值,作为比例。它指示了项目应该在flex容器中占据多少可用空间。

    如果所有项目的flex-grow设置为1,则容器中剩余的空间将平均分配给所有子元素。如果其中一个子元素的值为2,则剩余的空间将占据其他空间的两倍(或至少会尝试这样做)。 Grow

  • flex-shrink

    这定义了flex项目在必要时缩小的能力。

  • flex-basis

    这定义了元素在剩余空间分配之前的默认大小。它可以是一个长度(例如20%5rem等)或一个关键字。auto关键字意味着”查看我的宽度或高度属性”。

align-self#

align-self允许默认对齐(或由align-items指定的对齐)被单个flex项目覆盖。

Align

VBox和HBox助手#

VBox 和 HBox 助手类提供了简单的默认设置,以垂直和水平方式排列子小部件。它们大致相当于:

def VBox(*pargs, **kwargs):
    """Displays multiple widgets vertically using the flexible box model."""
    box = Box(*pargs, **kwargs)
    box.layout.display = 'flex'
    box.layout.flex_flow = 'column'
    box.layout.align_items = 'stretch'
    return box

def HBox(*pargs, **kwargs):
    """Displays multiple widgets horizontally using the flexible box model."""
    box = Box(*pargs, **kwargs)
    box.layout.display = 'flex'
    box.layout.align_items = 'stretch'
    return box

示例#

四个按钮在一个 VBox 中。项目在垂直盒子中伸展到最大宽度,该垂直盒子占据可用空间的 \(50\%\)

from ipywidgets import Layout, Button, Box

items_layout = Layout( width='auto')     # override the default width of the button to 'auto' to let the button grow

box_layout = Layout(display='flex',
                    flex_flow='column', 
                    align_items='stretch', 
                    border='solid',
                    width='50%')

words = ['correct', 'horse', 'battery', 'staple']
items = [Button(description=word, layout=items_layout, button_style='danger') for word in words]
box = Box(children=items, layout=box_layout)
box

三个按钮在一个 HBox 中。项目根据它们的重量比例进行弹性伸缩。

from ipywidgets import Layout, Button, Box, VBox

# Items flex proportionally to the weight and the left over space around the text 
items_auto = [
    Button(description='weight=1; auto', layout=Layout(flex='1 1 auto', width='auto'), button_style='danger'),
    Button(description='weight=3; auto', layout=Layout(flex='3 1 auto', width='auto'), button_style='danger'),
    Button(description='weight=1; auto', layout=Layout(flex='1 1 auto', width='auto'), button_style='danger'),
 ]

# Items flex proportionally to the weight 
items_0 = [
    Button(description='weight=1; 0%', layout=Layout(flex='1 1 0%', width='auto'), button_style='danger'),
    Button(description='weight=3; 0%', layout=Layout(flex='3 1 0%', width='auto'), button_style='danger'),
    Button(description='weight=1; 0%', layout=Layout(flex='1 1 0%', width='auto'), button_style='danger'),
 ]
box_layout = Layout(display='flex',
                    flex_flow='row', 
                    align_items='stretch', 
                    width='70%')
box_auto = Box(children=items_auto, layout=box_layout)
box_0 = Box(children=items_0, layout=box_layout)
VBox([box_auto, box_0])

一个更高级的例子:一个响应式表单。

该表单是一个宽度为 '50%'VBoxVBox 中的每一行都是一个 HBox,它使用空间间隔来对齐内容。

from ipywidgets import Layout, Button, Box, FloatText, Textarea, Dropdown, Label, IntSlider

form_item_layout = Layout(
    display='flex',
    flex_flow='row',
    justify_content='space-between'
)

form_items = [
    Box([Label(value='Age of the captain'), IntSlider(min=40, max=60)], layout=form_item_layout),
    Box([Label(value='Egg style'), 
         Dropdown(options=['Scrambled', 'Sunny side up', 'Over easy'])], layout=form_item_layout),
    Box([Label(value='Ship size'), 
         FloatText()], layout=form_item_layout),
    Box([Label(value='Information'), 
         Textarea()], layout=form_item_layout)
]

form = Box(form_items, layout=Layout(
    display='flex',
    flex_flow='column',
    border='solid 2px',
    align_items='stretch',
    width='50%'
))
form

一个更高级的例子:轮播图。

from ipywidgets import Layout, Button, VBox, Label

item_layout = Layout(height='100px', min_width='40px')
items = [Button(layout=item_layout, description=str(i), button_style='warning') for i in range(40)]
box_layout = Layout(overflow='scroll hidden',
                    border='3px solid black',
                    width='500px',
                    height='',
                    flex_flow='row',
                    display='flex')
carousel = Box(children=items, layout=box_layout)
VBox([Label('Scroll horizontally:'), carousel])

网格布局#

GridBox类是Box小部件的一个特例。

Box小部件实现了完整的CSS弹性盒子规范,使得在Jupyter笔记本中可以实现丰富的响应式布局。它旨在提供一种高效的方式,用于在容器中对项目进行布局、对齐和分配空间。

再次强调,整个网格布局规范可以通过容器小部件(Box)的layout属性以及包含的项目来暴露。可以在所有包含的项目之间共享相同的layout属性。

以下的弹性盒子教程遵循了Chris House的文章《A Complete Guide to Grid》的思路,并在获得许可的情况下,使用了该文章的文本和各种图像。

基础知识和浏览器支持#

要开始使用,你必须将一个容器元素定义为网格,使用display: grid,通过grid-template-rowsgrid-template-columnsgrid_template_areas设置行和列的大小,然后使用grid-columngrid-row将其子元素放置到网格中。与弹性盒子类似,网格项目的来源顺序并不重要。你的CSS可以以任何顺序放置它们,这使得使用媒体查询重新排列网格变得非常容易。想象一下定义整个页面的布局,然后完全重新安排它以适应不同的屏幕宽度,只需几行CSS代码。网格是迄今为止引入的最强大的CSS模块之一。

截至2017年3月,大多数浏览器都本机支持无前缀的CSS Grid:Chrome(包括在Android上)、Firefox、Safari(包括在iOS上)和Opera。另一方面,Internet Explorer 10和11支持它,但使用的是过时的语法。现在就是使用网格构建的时候!

重要术语#

在深入了解网格的概念之前,了解术语非常重要。由于这里涉及的术语在概念上都有些相似,如果你不先记住网格规范定义的含义,就很容易将它们混淆。不过别担心,它们并不多。

网格容器

应用display: grid的元素。它是所有网格项目的直接父级。在这个例子中,容器是网格容器。

<div class="container">
  <div class="item item-1"></div>
  <div class="item item-2"></div>
  <div class="item item-3"></div>
</div>

网格项目

网格容器的子元素(例如直接后代)。这里的item元素是网格项目,但sub-item不是。

<div class="container">
  <div class="item"></div> 
  <div class="item">
    <p class="sub-item"></p>
  </div>
  <div class="item"></div>
</div>

网格线

构成网格结构的分界线。它们可以是垂直的(“列网格线”)或水平的(“行网格线”),位于行或列的任一侧。这里的黄色线是列网格线的一个例子。

grid-line

网格轨道

两个相邻网格线之间的空间。你可以将它们看作是网格的列或行。这里是第二和第三行网格线之间的网格轨道。

grid-track

网格单元格

两个相邻行和两个相邻列网格线之间的空间。它是网格的一个”单元”。这里是行网格线1和2,以及列网格线2和3之间的网格单元格。

grid-cell

网格区域

由四条网格线包围的总空间。一个网格区域可能由任意数量的网格单元格组成。这里是行网格线1和3,以及列网格线1和3之间的网格区域。

grid-area

父级的属性#

grid-template-rows, grid-template-colums

使用空格分隔的值列表定义网格的列和行。这些值代表轨道大小,它们之间的空格代表网格线。

值:

  • <track-size> - 可以是长度、百分比或网格中可用空间的分数(使用fr单位)

  • <line-name> - 你选择的任意网格区域名称

grid-template-areas

通过引用使用grid-area属性指定的网格区域的名称来定义网格模板。重复网格区域的名称会导致内容跨越这些单元格。句点表示空单元格。语法本身提供了网格结构的可视化。

值:

  • <grid-area-name> - 使用grid-area指定的网格区域的名称

  • . - 句点表示空的网格单元格

  • none - 没有定义网格区域

grid-gap

grid-row-gapgrid-column-gap的简写

值:

  • <grid-row-gap>, <grid-column-gap> - 长度值

其中grid-row-gapgrid-column-gap指定网格线的大小。你可以将其视为设置列/行之间的间隙宽度。

  • <line-size> - 长度值

注意:grid-前缀将被删除,grid-gap将重命名为gap。未加前缀的属性已在Chrome 68+、Safari 11.2 Release 50+和Opera 54+中得到支持。

align-items

沿块(列)轴对齐网格项目(与justify-items相反,后者沿内联(行)轴对齐)。此值适用于容器内的所有网格项目。

值:

  • start - 使项目与单元格的起始边缘对齐

  • end - 使项目与单元格的结束边缘对齐

  • center - 在单元格中心对齐项目

  • stretch - 填充整个单元格的高度(这是默认值)

justify-items

沿内联(行)轴对齐网格项目(与align-items相反,后者沿块(列)轴对齐)。此值适用于容器内的所有网格项目。

值:

  • start - 使项目与单元格的起始边缘对齐

  • end - 使项目与单元格的结束边缘对齐

  • center - 在单元格中心对齐项目

  • stretch - 填充整个单元格的宽度(这是默认值)

align-content

有时,你的网格总大小可能小于其网格容器的大小。如果所有的网格项都使用非灵活的单位(如px)设置大小,就会发生这种情况。在这种情况下,你可以在网格容器内设置网格的对齐方式。这个属性沿着块(列)轴对齐网格(与justify-content相反,后者沿着内联(行)轴对齐)。

值:

  • start - 将网格与网格容器的起始边缘对齐

  • end - 将网格与网格容器的结束边缘对齐

  • center - 在网格容器的中心对齐网格

  • stretch - 调整网格项的大小以使网格填充满网格容器的高度

  • space-around - 在每个网格项之间放置相等的空间,两端有半尺寸的空间

  • space-between - 在每个网格项之间放置相等的空间,两端没有空间

  • space-evenly - 在每个网格项之间放置相等的空间,包括两端

justify-content

有时,你的网格总大小可能小于其网格容器的大小。如果所有的网格项都使用非灵活的单位(如px)设置大小,就会发生这种情况。在这种情况下,你可以在网格容器内设置网格的对齐方式。这个属性沿着内联(行)轴对齐网格(与align-content相反,后者沿着块(列)轴对齐)。

值:

  • start - 将网格与网格容器的起始边缘对齐

  • end - 将网格与网格容器的结束边缘对齐

  • center - 在网格容器的中心对齐网格

  • stretch - 调整网格项的大小以使网格填充满网格容器的宽度

  • space-around - 在每个网格项之间放置相等的空间,两端有半尺寸的空间

  • space-between - 在每个网格项之间放置相等的空间,两端没有空间

  • space-evenly - 在每个网格项之间放置相等的空间,包括两端

grid-auto-columns, grid-auto-rows

指定任何自动生成的网格轨道(又名隐式网格轨道)的大小。当网格中的网格项多于单元格或当一个网格项放置在显式网格之外时,会创建隐式轨道。(参见显式网格和隐式网格之间的区别)

值:

  • <track-size> - 可以是长度、百分比或网格中可用空间的分数(使用fr单位)

网格项的属性#

注意:floatdisplay: inline-blockdisplay: table-cellvertical-aligncolumn-??属性对网格项无效。

grid-column, grid-row

通过引用特定的网格线来确定网格项在网格中的位置。grid-column-start/grid-row-start是项开始的线,grid-column-end/grid-row-end是项结束的线。

值:

  • <line> - 可以是一个数字,引用编号的网格线,或者是一个名称,引用命名的网格线

  • span <number> - 项将跨越提供的数目的网格轨道

  • span <name> - 项将一直跨越直到遇到下一个具有提供名称的线

  • auto - 表示自动放置,自动跨度或默认跨度为一

.item {
  grid-column: <number> | <name> | span <number> | span <name> | auto / 
               <number> | <name> | span <number> | span <name> | auto
  grid-row: <number> | <name> | span <number> | span <name> | auto /
            <number> | <name> | span <number> | span <name> | auto
}

示例:

.item-a {
  grid-column: 2 / five;
  grid-row: row1-start / 3;
}

grid-start-end-a

.item-b {
  grid-column: 1 / span col4-start;
  grid-row: 2 / span 2;
}

grid-start-end-b

如果没有声明grid-column / grid-row,项将默认跨越1个轨道。

项可以重叠。你可以使用z-index来控制它们的堆叠顺序。

grid-area

给一个项命名,以便可以通过grid-template-areas属性创建的模板引用它。或者,这个属性可以作为grid-row-start + grid-column-start + grid-row-end + grid-column-end的更简短的简写。

值:

  • <name> - 你选择的名称

  • <row-start> / <column-start> / <row-end> / <column-end> - 可以是数字或命名的线条

.item {
  grid-area: <name> | <row-start> / <column-start> / <row-end> / <column-end>;
}

示例:

作为给项分配名称的方式:

.item-d {
  grid-area: header
}

作为grid-row-start + grid-column-start + grid-row-end + grid-column-end的简写:

.item-d {
  grid-area: 1 / col4-start / last-line / 6
}

grid-start-end-d

justify-self

沿着内联(行)轴对齐单元格内的网格项(与align-self相反,后者沿着块(列)轴对齐)。此值适用于单个单元格内的网格项。

值:

  • start - 将网格项与单元格的起始边缘对齐

  • end - 将网格项与单元格的结束边缘对齐

  • center - 在单元格的中心对齐网格项

  • stretch - 填充整个单元格的宽度(这是默认值)

.item {
  justify-self: start | end | center | stretch;
}

示例:

.item-a {
  justify-self: start;
}

Example of  set to start

.item-a {
  justify-self: end;
}

Example of  set to end

.item-a {
  justify-self: center;
}

Example of  set to center

.item-a {
  justify-self: stretch;
}

Example of  set to stretch

要为网格中的所有项设置对齐方式,也可以通过网格容器上的justify-items属性来设置此行为。

from ipywidgets import Button, GridBox, Layout, ButtonStyle

按名称放置项目:

header  = Button(description='Header',
                 layout=Layout(width='auto', grid_area='header'),
                 style=ButtonStyle(button_color='lightblue'))
main    = Button(description='Main',
                 layout=Layout(width='auto', grid_area='main'),
                 style=ButtonStyle(button_color='moccasin'))
sidebar = Button(description='Sidebar',
                 layout=Layout(width='auto', grid_area='sidebar'),
                 style=ButtonStyle(button_color='salmon'))
footer  = Button(description='Footer',
                 layout=Layout(width='auto', grid_area='footer'),
                 style=ButtonStyle(button_color='olive'))

GridBox(children=[header, main, sidebar, footer],
        layout=Layout(
            width='50%',
            grid_template_rows='auto auto auto',
            grid_template_columns='25% 25% 25% 25%',
            grid_template_areas='''
            "header header header header"
            "main main . sidebar "
            "footer footer footer footer"
            ''')
       )

设置行和列模板及间距

GridBox(children=[Button(layout=Layout(width='auto', height='auto'),
                         style=ButtonStyle(button_color='darkseagreen')) for i in range(9)
                 ],
        layout=Layout(
            width='50%',
            grid_template_columns='100px 50px 100px',
            grid_template_rows='80px auto 80px', 
            grid_gap='5px 10px')
       )

图像布局和大小调整与其他元素略有不同,这是由于历史原因(HTML标签img在CSS之前就存在了)和实际原因(图像具有固有的大小)。

图像大小调整尤其令人困惑,因为有两种方式可以指定图像的大小。Image小部件具有与HTML img标签同名的属性widthheight。此外,Image小部件(像其他所有小部件一样)具有layout,它也具有widthheight

另外,一些CSS样式被应用到图像上,而这些样式不会被应用到其他小部件上:max_width被设置为100%height被设置为auto

你不应该依赖Image.widthImage.height来确定图像小部件的显示宽度和高度。任何CSS样式,无论是通过Image.layout还是其他来源,都会覆盖Image.widthImage.height

当单独显示一个Image小部件时,将Image.layout.width设置为所需的宽度将按原始图像的宽高比显示Image小部件。

当将Image放入Box(或HBoxVBox)中时,结果取决于是否在Box上设置了宽度。如果设置了宽度,那么图像将被拉伸(或压缩)以适应盒子,因为Image.layout.max_width100%,所以图像会填充容器。这通常不会保留图像的宽高比。

控制容器内Image的显示#

使用Image.layout.object_fit来控制图像在容器(如盒子)内的缩放方式。可能的取值有:

  • 'contain':在保持宽高比的同时,将图像适应到其内容框中。如果容器的任何部分没有被图像覆盖,则显示容器的背景。内容框的大小取决于容器和图像的大小,如果容器小于图像,则为容器的大小;如果容器大于图像,则为图像的大小。

  • 'cover':在保持图像宽高比的同时,完全填充内容框,必要时裁剪图像。

  • 'fill':完全填充内容框,根据需要拉伸/压缩图像。

  • 'none':不进行大小调整;图像将被容器裁剪。

  • 'scale-down':执行与containnone相同的操作,使用两者中显示图像较小的那个。

  • None(Python值):从布局中移除object_fit;效果与'fill'相同。

使用Image.layout.object_position来控制在容器(如盒子)内图像的位置。默认值确保图像在盒子中居中。在某些情况下,Image.layout.object_position的效果取决于Image.layout.object_fit的值。

可以通过下面描述的几种方式来指定object_position的值。

object_fit的示例#

在下面的例子中,一个图像被显示在一个绿色盒子内,以展示object_fit每个值的效果。

为了让示例保持一致,这里定义通用的代码。

from ipywidgets import Layout, Box, VBox, HBox, HTML, Image

fit_options = ['contain', 'cover', 'fill', 'scale-down', 'none', None]

hbox_layout = Layout()
hbox_layout.width = '100%'
hbox_layout.justify_content = 'space-around'

green_box_layout = Layout()
green_box_layout.width = '100px'
green_box_layout.height = '100px'
green_box_layout.border = '2px solid green'


def make_box_for_grid(image_widget, fit):
    """
    Make a VBox to hold caption/image for demonstrating
    option_fit values.
    """
    # Make the caption
    if fit is not None:
        fit_str = "'{}'".format(fit)
    else:
        fit_str = str(fit)
        
    h = HTML(value='' + str(fit_str) + '')

    # Make the green box with the image widget inside it
    boxb = Box()
    boxb.layout = green_box_layout
    boxb.children = [image_widget]
    
    # Compose into a vertical box
    vb = VBox()
    vb.layout.align_items = 'center'
    vb.children = [h, boxb]
    return vb

# Use this margin to eliminate space between the image and the box
image_margin = '0 0 0 0'

# Set size of captions in figures below
caption_size = 'h4'

object_fit在比原始图像小的Box#

在下面的图像中可以看到每个效果。在每种情况下,图像位于一个带有绿色边框的盒子中。原始图像是600x300,图像中的网格盒子是正方形。由于图像比盒子宽度宽,内容框的大小为容器的大小。

with open('images/gaussian_with_grid.png', 'rb') as f:
    im_600_300 = f.read()
boxes = []
for fit in fit_options:
    ib = Image(value=im_600_300)
    ib.layout.object_fit = fit
    ib.layout.margin = image_margin

    boxes.append(make_box_for_grid(ib, fit))

vb = VBox()
h = HTML(value='<{size}>Examples of <code>object_fit</code> with large image</{size}>'.format(size=caption_size))
vb.layout.align_items = 'center'
hb = HBox()
hb.layout = hbox_layout
hb.children = boxes

vb.children = [h, hb]
vb

object_fit在比原始图像大的Box#

在下面的图像中可以看到每个效果。在每种情况下,图像位于一个带有绿色边框的盒子中。原始图像是50x25,图像中的网格盒子是正方形。

with open('images/gaussian_with_grid_tiny.png', 'rb') as f:
    im_50_25 = f.read()
boxes = []
for fit in fit_options:
    ib = Image(value=im_50_25)
    ib.layout.object_fit = fit
    ib.layout.margin = image_margin
    boxes.append(make_box_for_grid(ib, fit))

vb = VBox()
h = HTML(value='<{size}>Examples of <code>object_fit</code> with small image</{size}>'.format(size=caption_size))
vb.layout.align_items = 'center'
hb = HBox()
hb.layout = hbox_layout
hb.children = boxes

vb.children = [h, hb]
vb

根据option_fit的值的描述,可能会让人感到惊讶的是,在所有情况下,图像实际上都没有填充整个盒子。原因是底层图像只有50像素宽,是盒子宽度的一半,所以fillcover的意思是“填充/覆盖由图像大小决定的内容框”。

object_fit在比原始图像大的Box中:使用图像布局宽度100%来填充容器#

如果图像的布局宽度设置为100%,它将填充放置它的盒子。这个例子还展示了'contain''scale-down'之间的区别。'scale-down'的效果与'contain''none'相同,取决于哪个导致显示的图像更小。在这种情况下,较小的图像来自于不进行适配,所以这就是显示出来的结果。

boxes = []
for fit in fit_options:
    ib = Image(value=im_50_25)
    ib.layout.object_fit = fit
    ib.layout.margin = image_margin

    # NOTE WIDTH IS SET TO 100%
    ib.layout.width = '100%'
    
    boxes.append(make_box_for_grid(ib, fit))

vb = VBox()
h = HTML(value='<{size}>Examples of <code>object_fit</code> with image '
               'smaller than container</{size}>'.format(size=caption_size))
vb.layout.align_items = 'center'
hb = HBox()
hb.layout = hbox_layout
hb.children = boxes

vb.children = [h, hb]
vb

object_position的示例#

有几种方法可以设置对象位置:

  • 使用关键字,如topleft,来描述图像应如何放置在容器中。

  • 使用两个位置值(以像素为单位),表示从容器的左上角到图像的左上角的偏移量。偏移量可以是正数或负数,可以用来将图像定位在盒子外部。

  • 在每个方向上使用百分比作为偏移量。如果图像小于容器,则百分比是指围绕图像的垂直或水平留白部分的比例;如果图像大于容器,则是指图像超出容器的部分的比例。

  • 混合使用像素和百分比偏移量。

  • 混合使用关键字和偏移量。

object_fit决定的图像缩放在某些情况下会优先于定位。例如,如果object_fitfill,以便图像应该填充容器而不保持宽高比,则object_position将无效,因为实际上没有定位要执行。

另一种思考方式是:object_position指定了如果在某个方向上有留白,应该如何在容器中分配围绕图像的空白空间。

使用关键字指定object_position#

这种形式的object_position接受两个关键字,一个用于指定图像在容器中的水平位置,另一个用于垂直位置,顺序如此。

  • 水平位置必须是以下之一:

    • 'left':图像的左侧应与容器的左侧对齐

    • 'center':图像应在容器中水平居中

    • 'right':图像的右侧应与容器的右侧对齐

  • 垂直位置必须是以下之一:

    • 'top':图像的顶部应与容器的顶部对齐

    • 'center':图像应在容器中垂直居中

    • 'bottom':图像的底部应与容器的底部对齐

每种效果都会在下面展示,一次是对于小于容器的图像,一次是对于大于容器的图像。

在下面的示例中,object_fit被设置为'none',以便图像不被缩放。

object_fit = 'none'
image_value = [im_600_300, im_50_25]
horz_keywords = ['left', 'center', 'right']
vert_keywords = ['top', 'center', 'bottom']

rows = []
for image, caption  in zip(image_value, ['600 x 300 image', '50 x 25 image']):
    cols = []
    for horz in horz_keywords:
        for vert in vert_keywords:
            ib = Image(value=image)
            ib.layout.object_position = '{horz} {vert}'.format(horz=horz, vert=vert)
            ib.layout.margin = image_margin
            ib.layout.object_fit = object_fit
            # ib.layout.height = 'inherit'
            ib.layout.width = '100%'
            cols.append(make_box_for_grid(ib, ib.layout.object_position))
    hb = HBox()
    hb.layout = hbox_layout
    hb.children = cols
    rows.append(hb)

vb = VBox()

h1 = HTML(value='<{size}><code> object_position </code> by '
                'keyword with large image</{size}>'.format(size=caption_size))
h2 = HTML(value='<{size}><code> object_position </code> by '
                'keyword with small image</{size}>'.format(size=caption_size))

vb.children = [h1, rows[0], h2, rows[1]]
vb.layout.height = '400px'
vb.layout.justify_content = 'space-around'
vb.layout.align_items = 'center'
vb

使用像素偏移指定object_position#

可以指定图像的左上角相对于容器的左上角的像素偏移。两个偏移值中的第一个是水平的,第二个是垂直的,两者都可以为负数。如果偏移量足够大以至于图像位于容器外部,将导致图像被隐藏。

首先使用object_fit的值(如果未指定任何内容,则默认为fill)对图像进行缩放,然后应用偏移。

可以通过组合关键字和像素偏移来从底部和/或右侧指定偏移。例如,right 10px bottom 20px将图像的右侧从容器的右边缘偏移10px,并将图像的底部从容器的底部偏移20px。

object_fit = ['none', 'contain', 'fill', 'cover']
offset = '20px 10px'
image_value = [im_600_300]

boxes = []
for image, caption  in zip(image_value, ['600 x 300 image', ]):
    for fit in object_fit:
        ib = Image(value=image)
        ib.layout.object_position = offset
        ib.layout.margin = image_margin
        ib.layout.object_fit = fit
        # ib.layout.height = 'inherit'
        ib.layout.width = '100%'
        title = 'object_fit: {}'.format(ib.layout.object_fit)
        boxes.append(make_box_for_grid(ib, title))

vb = VBox()
h = HTML(value='<{size}><code>object_position</code> by '
               'offset {offset} with several '
               '<code>object_fit</code>s with large image</{size}>'.format(size=caption_size,
                                                         offset=offset))
vb.layout.align_items = 'center'
hb = HBox()
hb.layout = hbox_layout
hb.children = boxes

vb.children = [h, hb]
vb

使用百分比偏移指定object_position#

可以以百分比的形式指定图像的左上角相对于容器的左上角的偏移量。两个偏移值中的第一个是水平的,第二个是垂直的,两者都可以为负数。如果偏移量足够大以至于图像位于容器外部,将导致图像被隐藏。

重要的是要理解,如果图像小于容器,这是每个方向上留白空间的百分比,因此50% 50%会使图像居中。

如果在使用object_fit缩放后图像大于容器,则偏移量是图像超出容器部分的百分比。这意味着50% 50%也会使一个大于容器的图像居中。10% 90%的值会将图像超出容器部分的10%放在左边边缘的左侧,以及顶部边缘上方的90%。

与通过关键字指定object_position一样,object_fit可能会阻止任何偏移应用于图像。