代码演示
如何引入
import { Resizable } from '@fibrous/semi-ui-solid';
import { ResizeItem, ResizeHandler, ResizeGroup } from '@fibrous/semi-ui-solid';
单个组件 基本使用
通过defaultSize设置初始大小,可以通过onResizeStart、onChange、onResizeEnd设置拖拽的回调
interface Size {
width?: string | number;
height?: string | number;
}
代码
import { createSignal } from 'solid-js';
import { Resizable, Toast } from '@fibrous/semi-ui-solid';
const startToast = {
content: 'resize start',
duration: 1,
stack: true,
};
const endToast = {
content: 'resize end',
duration: 1,
stack: true,
};
export default function ResizableDemo() {
const [text, setText] = createSignal('Drag edge to resize');
return (
<div style={{ width: '500px' }}>
<Resizable
style={{ 'background-color': 'var(--semi-grey-1)' }}
defaultSize={{
width: '60%',
height: 300,
}}
onChange={() => {
setText('resizing');
}}
onResizeStart={() => {
Toast.info(startToast);
}}
onResizeEnd={() => {
Toast.info(endToast);
setText('Drag edge to resize');
}}
>
<div style={{ 'margin-left': '20%' }}>{text()}</div>
</Resizable>
</div>
);
}控制伸缩方向
通过设置enable的值开启/关闭特定伸缩方向,默认值均为true;未写出的方向视为开启,要只开启部分方向,需把其余方向显式设为 false。enable 传 false 时不渲染任何手柄。
interface Enable {
left?: boolean;
right?: boolean;
top?: boolean;
bottom?: boolean;
topLeft?: boolean;
topRight?: boolean;
bottomLeft?: boolean;
bottomRight?: boolean;
}
disable
代码
import { createSignal } from 'solid-js';
import { Resizable, Switch, Typography } from '@fibrous/semi-ui-solid';
export default function ResizableDemo() {
const [b, setB] = createSignal(false);
return (
<div style={{ width: '500px', height: '60%' }}>
<div style={{ display: 'flex', 'align-items': 'center', margin: '8px' }}>
<Switch aria-labelledby="resizable-enable-state" checked={b()} onChange={(checked) => setB(checked)} />
<Typography.Title id="resizable-enable-state" heading={6} style={{ margin: '8px' }}>
{b() ? 'able' : 'disable'}
</Typography.Title>
</div>
<Resizable
style={{ 'background-color': 'var(--semi-grey-1)' }}
enable={{
left: b(),
}}
defaultSize={{
width: 200,
height: 200,
}}
>
<div style={{ 'margin-left': '20%' }}>{'enable.left:' + b()}</div>
</Resizable>
</div>
);
}设置变化比例
通过ratio设置拖动和实际变化的比例
代码
import { Resizable } from '@fibrous/semi-ui-solid';
export default function ResizableDemo() {
return (
<div style={{ width: '500px', height: '60%' }}>
<Resizable
style={{ 'background-color': 'var(--semi-grey-1)' }}
ratio={2}
defaultSize={{
width: 200,
height: 200,
}}
>
<div style={{ 'margin-left': '20%' }}>ratio=2</div>
</Resizable>
</div>
);
}锁定横纵比
通过lockAspectRatio设置锁定横纵比,可以为boolean或number,为number时表示横纵比为number,为true时锁定初始横纵比
代码
import { Resizable } from '@fibrous/semi-ui-solid';
export default function ResizableDemo() {
return (
<div style={{ width: '500px', height: '60%' }}>
<Resizable
style={{ 'background-color': 'var(--semi-grey-1)', 'margin-bottom': '10px' }}
defaultSize={{
width: 400,
height: 300,
}}
lockAspectRatio
>
<div style={{ 'margin-left': '20%' }}>lock</div>
</Resizable>
<Resizable
style={{ 'background-color': 'var(--semi-grey-1)' }}
defaultSize={{
width: 200,
height: (200 * 9) / 16,
}}
lockAspectRatio={16 / 9}
>
<div style={{ 'margin-left': '20%' }}>16 / 9</div>
</Resizable>
</div>
);
}设置最大,最小宽高
可通过 maxHeight,maxWidth,minHeight,minWidth 设置最大,最小宽高
代码
import { Resizable } from '@fibrous/semi-ui-solid';
export default function ResizableDemo() {
return (
<div style={{ width: '500px', height: '60%' }}>
<Resizable
style={{ 'background-color': 'var(--semi-grey-1)' }}
maxWidth={200}
maxHeight={300}
minWidth={50}
minHeight={50}
defaultSize={{
width: 100,
height: 100,
}}
>
<div style={{ 'margin-left': '20%' }}>width在50到200之间,height在50到300之间</div>
</Resizable>
</div>
);
}受控宽高
可通过 size 控制元素的宽高
拖动时需在 onChange 中把新尺寸写回 size 对应的信号,尺寸才会随拖拽变化;未写回时,尺寸保持为 size 的值。
代码
import { createSignal } from 'solid-js';
import { Button, Resizable, type ResizableProps } from '@fibrous/semi-ui-solid';
type Size = NonNullable<ResizableProps['size']>;
export default function ResizableDemo() {
const [size, setSize] = createSignal<Size>({ width: 200, height: 100 });
const onButtonClick = () => {
const current = size();
setSize({ width: Number(current.width) + 10, height: Number(current.height) + 10 });
};
return (
<div style={{ width: '500px', height: '60%' }}>
<Button onClick={onButtonClick}>set += 10</Button>
<Resizable style={{ 'background-color': 'var(--semi-grey-1)', 'margin-top': '10px' }} size={size()} onChange={(next) => setSize(next)}>
<div style={{ 'margin-left': '20%' }}>受控</div>
</Resizable>
</div>
);
}设置缩放值
通过设置 scale,整体缩放元素
代码
import { Resizable } from '@fibrous/semi-ui-solid';
export default function ResizableDemo() {
return (
<div style={{ width: '500px', height: '60%', transform: 'scale(0.5)', 'transform-origin': '0 0' }}>
<Resizable
style={{ 'background-color': 'var(--semi-grey-1)' }}
defaultSize={{
width: '60%',
height: '60%',
}}
scale={0.5}
>
<div style={{ 'margin-left': '20%' }}>scale 0.5</div>
</Resizable>
</div>
);
}根据元素限制元素宽高
通过 boundElement 设置用于限制宽高的元素,支持 'parent'、'window' 或一个 HTMLElement
代码
import { Resizable } from '@fibrous/semi-ui-solid';
export default function ResizableDemo() {
return (
<div style={{ width: '300px', height: '300px', border: 'var(--semi-color-border) 1px solid' }}>
<Resizable
style={{ 'margin-left': '20%', 'background-color': 'var(--semi-grey-1)' }}
defaultSize={{
width: '60%',
height: 200,
}}
boundElement="parent"
>
<div style={{ 'margin-left': '20%' }}>bound:parent</div>
</Resizable>
</div>
);
}自定义边角 handler 样式
可通过 renderHandle 设置各方向的拖动元素节点:它对每个启用的方向调用一次,参数为该手柄的方向,按方向返回内容;返回空内容的方向仍保留手柄本身。可通过 handleStyle,handleClass 设置不同方向上的样式,样式可传对象或字符串。
type RenderHandle = (direction: Direction) => JSX.Element;
type HandleStyle = {
left?: JSX.CSSProperties | string;
right?: JSX.CSSProperties | string;
top?: JSX.CSSProperties | string;
bottom?: JSX.CSSProperties | string;
topLeft?: JSX.CSSProperties | string;
topRight?: JSX.CSSProperties | string;
bottomLeft?: JSX.CSSProperties | string;
bottomRight?: JSX.CSSProperties | string;
};
type HandleClass = {
left?: string;
right?: string;
top?: string;
bottom?: string;
topLeft?: string;
topRight?: string;
bottomLeft?: string;
bottomRight?: string;
};
代码
import { IconTransfer } from '@fibrous/semi-icons-solid/lab';
import { Resizable } from '@fibrous/semi-ui-solid';
export default function ResizableDemo() {
return (
<div style={{ width: '500px', height: '60%' }}>
<Resizable
style={{
'margin-left': '20%',
'background-color': 'var(--semi-grey-1)',
border: 'var(--semi-color-border) 1px solid',
}}
defaultSize={{
width: '60%',
height: 300,
}}
renderHandle={(direction) =>
direction === 'right' ? (
<div
style={{
height: '100%',
display: 'flex',
'align-items': 'center',
width: 'fit-content',
}}
>
<IconTransfer />
</div>
) : undefined
}
>
<div style={{ 'margin-left': '20%' }}>right</div>
</Resizable>
</div>
);
}允许阶段性调整宽高
可通过 grid ,snap 属性允许逐渐调整宽高。 grid 属性用于指定调整大小应对齐的增量。默认为 [1, 1]。 snap 属性用于指定调整大小时应对齐的绝对像素值。 x 和 y 都是可选的,允许仅包含要定义的轴。默认为空。以上两个参数可结合 snapGap 使用,该参数用于指定移动到下一个目标所需的最小间隙。默认为 0,这意味着始终使用 grid/snap 设定的目标。
interface Snap {
x?: number[];
y?: number[];
}
代码
import { Resizable } from '@fibrous/semi-ui-solid';
export default function ResizableDemo() {
return (
<div style={{ width: '500px', height: '60%' }}>
<Resizable
style={{
'margin-left': '20%',
'background-color': 'var(--semi-grey-1)',
border: 'var(--semi-color-border) 1px solid',
}}
defaultSize={{
width: '60%',
height: 300,
}}
grid={[100, 100]}
snapGap={20}
>
<div style={{ 'margin-left': '20%' }}>snap</div>
</Resizable>
</div>
);
}组合组件 基本使用
通过direction设置伸缩方向,可选值为horizontal和vertical,缺省为horizontal。支持onResizeStart、onChange、onResizeEnd回调,支持min、max设置最大最小宽高
代码
import { createSignal } from 'solid-js';
import { ResizeGroup, ResizeHandler, ResizeItem } from '@fibrous/semi-ui-solid';
const boxStyle = {
'background-color': 'var(--semi-grey-1)',
border: 'var(--semi-color-border) 1px solid',
};
const textStyle = {
'margin-left': '20%',
};
export default function ResizableDemo() {
const [text, setText] = createSignal('Drag to resize');
return (
<div style={{ width: '1000px', height: '100px' }}>
<ResizeGroup direction="horizontal">
<ResizeItem
style={boxStyle}
defaultSize="400px"
min="10%"
onChange={() => {
setText('resizing');
}}
onResizeEnd={() => {
setText('Drag to resize');
}}
>
<div style={textStyle}>{text() + ' min:10%'}</div>
</ResizeItem>
<ResizeHandler />
<ResizeItem
style={boxStyle}
defaultSize="20%"
min="10%"
max="30%"
onChange={() => {
setText('resizing');
}}
>
<div style={textStyle}>{text() + ' min:10% max:30%'}</div>
</ResizeItem>
<ResizeHandler />
<ResizeItem
style={boxStyle}
defaultSize="0.5"
onChange={() => {
setText('resizing');
}}
>
<div style={textStyle}>{text()}</div>
</ResizeItem>
<ResizeHandler />
<ResizeItem
style={boxStyle}
defaultSize={1}
onChange={() => {
setText('resizing');
}}
>
<div style={textStyle}>{text()}</div>
</ResizeItem>
</ResizeGroup>
</div>
);
}嵌套使用
通过direction设置伸缩方向,可选值为horizontal和vertical
代码
import { createSignal } from 'solid-js';
import { ResizeGroup, ResizeHandler, ResizeItem, Toast } from '@fibrous/semi-ui-solid';
const boxStyle = {
'background-color': 'var(--semi-grey-1)',
border: 'var(--semi-color-border) 1px solid',
};
const textStyle = {
'margin-left': '20%',
};
const startToast = {
content: 'resize start',
duration: 1,
stack: true,
};
const endToast = {
content: 'resize end',
duration: 1,
stack: true,
};
export default function ResizableDemo() {
const [text, setText] = createSignal('Drag to resize');
return (
<div style={{ width: '1000px', height: '600px' }}>
<ResizeGroup direction="vertical">
<ResizeItem
style={{ 'background-color': 'var(--semi-grey-1)' }}
defaultSize="20%"
onChange={() => {
setText('resizing');
}}
onResizeStart={() => {
Toast.info(startToast);
}}
onResizeEnd={() => {
Toast.info(endToast);
setText('Drag to resize');
}}
>
<div style={textStyle}>header</div>
</ResizeItem>
<ResizeHandler />
<ResizeItem
defaultSize="80%"
onChange={() => {
setText('resizing');
}}
>
<ResizeGroup direction="horizontal">
<ResizeItem
style={boxStyle}
defaultSize="25%"
onChange={() => {
setText('resizing');
}}
onResizeStart={() => {
Toast.info(startToast);
}}
onResizeEnd={() => {
Toast.info(endToast);
setText('Drag to resize');
}}
>
<div style={textStyle}>tab</div>
</ResizeItem>
<ResizeHandler />
<ResizeItem
style={boxStyle}
defaultSize="75%"
onChange={() => {
setText('resizing');
}}
>
<div style={textStyle}>{text()}</div>
</ResizeItem>
</ResizeGroup>
</ResizeItem>
</ResizeGroup>
</div>
);
}代码
import { createSignal } from 'solid-js';
import { ResizeGroup, ResizeHandler, ResizeItem } from '@fibrous/semi-ui-solid';
const boxStyle = {
'background-color': 'var(--semi-grey-1)',
border: 'var(--semi-color-border) 1px solid',
};
const frameStyle = {
border: 'var(--semi-color-border) 1px solid',
};
const textStyle = {
'margin-left': '20%',
};
export default function ResizableDemo() {
const [text, setText] = createSignal('Drag to resize');
return (
<div style={{ width: '1000px', height: '600px' }}>
<ResizeGroup direction="vertical">
<ResizeItem defaultSize="80%">
<ResizeGroup direction="horizontal">
<ResizeItem style={boxStyle} defaultSize="25%" min="10%" max="30%">
<div style={textStyle}>{text() + ' min:10% max:30%'}</div>
</ResizeItem>
<ResizeHandler />
<ResizeItem style={frameStyle} defaultSize="50%">
<div style={{ height: '100%' }}>
<ResizeGroup direction="vertical">
<ResizeItem
style={boxStyle}
defaultSize="33%"
min="10%"
onChange={() => {
setText('resizing');
}}
onResizeEnd={() => {
setText('Drag to resize');
}}
>
<div style={textStyle}>{text() + ' min:10%'}</div>
</ResizeItem>
<ResizeHandler />
<ResizeItem style={boxStyle} defaultSize="33%" min="10%" max="40%">
<div style={textStyle}>{text() + ' min:10% max:40%'}</div>
</ResizeItem>
<ResizeHandler />
<ResizeItem style={boxStyle}>
<div style={textStyle}>{text()}</div>
</ResizeItem>
</ResizeGroup>
</div>
</ResizeItem>
<ResizeHandler />
<ResizeItem style={boxStyle} defaultSize="1" max="30%">
<div style={textStyle}>{text() + ' max:30%'}</div>
</ResizeItem>
</ResizeGroup>
</ResizeItem>
<ResizeHandler />
<ResizeItem
defaultSize="20%"
onChange={() => {
setText('resizing');
}}
>
<ResizeGroup direction="horizontal">
<ResizeItem style={boxStyle} defaultSize="50%">
<div style={textStyle}>tab</div>
</ResizeItem>
<ResizeHandler />
<ResizeItem style={boxStyle} defaultSize="50%">
<div style={textStyle}>content</div>
</ResizeItem>
</ResizeGroup>
</ResizeItem>
</ResizeGroup>
</div>
);
}动态方向
代码
import { createSignal } from 'solid-js';
import { Button, ResizeGroup, ResizeHandler, ResizeItem } from '@fibrous/semi-ui-solid';
const boxStyle = {
'background-color': 'var(--semi-grey-1)',
};
const textStyle = {
'margin-left': '20%',
padding: '5px',
};
export default function ResizableDemo() {
const [text, setText] = createSignal('drag to resize');
const [direction, setDirection] = createSignal<'horizontal' | 'vertical'>('horizontal');
const changeDirection = () => {
setDirection(direction() === 'horizontal' ? 'vertical' : 'horizontal');
};
return (
<div style={{ width: '400px', height: '300px' }}>
<Button onClick={changeDirection}>{direction()}</Button>
<ResizeGroup direction={direction()}>
<ResizeItem
onChange={() => {
setText('resizing');
}}
onResizeEnd={() => {
setText('drag to resize');
}}
defaultSize={5}
>
<ResizeGroup direction="horizontal">
<ResizeItem
style={boxStyle}
onChange={() => {
setText('resizing');
}}
onResizeEnd={() => {
setText('drag to resize');
}}
>
<div style={textStyle}>{text()}</div>
</ResizeItem>
<ResizeHandler />
<ResizeItem
style={boxStyle}
onChange={() => {
setText('resizing');
}}
>
<div style={textStyle}>{text()}</div>
</ResizeItem>
</ResizeGroup>
</ResizeItem>
<ResizeHandler />
<ResizeItem
style={boxStyle}
defaultSize={1.3}
onChange={() => {
setText('resizing');
}}
>
<div style={textStyle}>{text()}</div>
</ResizeItem>
</ResizeGroup>
</div>
);
}API 参考
Resizable
单个伸缩框组件。
Props
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| boundElement | 限制宽高的元素:'parent'、'window' 或一个 HTMLElement。 | 'parent' | 'window' | HTMLElement | - |
| boundsByDirection | 是否按拖动方向计算边界:为 true 时,从左侧或上侧手柄拖动,最大尺寸按元素固定的那一侧到限制元素对侧边缘的距离计算。 | boolean | - |
| defaultSize | 初始宽高,支持数字和字符串(px/vw/vh/%)。 | Size | - |
| enable | 可伸缩的方向,未设置为 false 的方向均可拖动;传入 false 则不渲染任何手柄。 | Enable | false | Type{
top: true,
right: true,
bottom: true,
left: true,
topRight: true,
bottomRight: true,
bottomLeft: true,
topLeft: true,
} |
| grid | 调整大小时对齐的增量。 | [number, number] | [1, 1] |
| handleClass | 各方向手柄的类名。 | HandleClassName | - |
| handleStyle | 各方向手柄的样式,可传对象或字符串。 | HandleStyle | - |
| handleWrapperClass | 包裹所有手柄的容器类名。 | string | - |
| handleWrapperStyle | 包裹所有手柄的容器样式。 | JSX.CSSProperties | string | - |
| lockAspectRatio | 锁定横纵比:true 按初始宽高锁定,数字为宽高比。 | boolean | number | false |
| lockAspectRatioExtraHeight | 锁定横纵比时,不参与比例计算的额外高度(如内边距),单位为像素。 | number | 0 |
| lockAspectRatioExtraWidth | 锁定横纵比时,不参与比例计算的额外宽度(如内边距),单位为像素。 | number | 0 |
| maxHeight | 最大高度。 | string | number | - |
| maxWidth | 最大宽度。 | string | number | - |
| minHeight | 最小高度。 | string | number | - |
| minWidth | 最小宽度。 | string | number | - |
| onChange | 拖动或方向键改变尺寸时调用,参数为新尺寸、事件与方向。 | ResizeCallback | - |
| onResizeEnd | 结束伸缩时调用,参数为最终尺寸、事件与方向。 | ResizeCallback | - |
| onResizeStart | 开始伸缩时调用,返回 false 取消本次伸缩。 | ResizeStartCallback | - |
| ratio | 拖动距离与实际尺寸变化的比例,可传 [宽, 高]。方向键每次固定改变 10px,不受 ratio 与 scale 影响。 | number | [number, number] | 1 |
| renderHandle | 各启用方向手柄的内容,替换默认的握把图标;每个启用的方向调用一次,参数为该手柄的方向。 | Type(direction: Direction) => JSX.Element | - |
| scale | 元素被外部缩放的比例,用于校正指针位移。 | number | 1 |
| size | 受控尺寸,按轴生效:未设置的轴仍跟随拖拽。拖动时需在 onChange 中把新尺寸写回。 | Size | - |
| snap | 调整大小时对齐的绝对像素值,x 与 y 均可省略。 | { x?: number[]; y?: number[]; } | - |
| snapGap | 移动到下一个对齐目标所需的最小间隙。 | number | 0 |
class、style、ref 与原生属性经 rest 传入,落在根元素上;根元素的 style 先于组件计算出的尺寸应用,所以传入的 width、height 不会让拖拽失效。
ResizeGroup
Props
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| direction | 组内的伸缩方向,可选 horizontal 或 vertical。 | 'horizontal' | 'vertical' | "horizontal" |
class、style、ref 与原生属性经 rest 传入,落在根元素上;flex-direction 始终由 direction 决定。
ResizeHandler
替换默认握把图标的内容通过 children 传入。class、style 与原生属性经 rest 传入,落在手柄元素上;传入的 aria-label 优先于语言包名称,传入的指针与键盘回调会和组件自己的处理一并执行。
ResizeItem
Props
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| defaultSize | 初始尺寸。字符串支持 %、px、vw、vh;纯数字或数字字符串表示按比例分配剩余空间。 | string | number | - |
| max | 最大尺寸(%、px、vw 或 vh)。 | string | - |
| min | 最小尺寸(%、px、vw 或 vh)。 | string | - |
| onChange | 拖动或方向键改变尺寸时调用,参数为新尺寸、事件与方向。 | ResizeCallback | - |
| onResizeEnd | 结束伸缩时调用,参数为最终尺寸、事件与方向。 | ResizeCallback | - |
| onResizeStart | 开始伸缩时调用,手柄任一侧的项返回 false 即取消本次伸缩。 | ResizeStartCallback | - |
class、style 与原生属性经 rest 传入,落在根元素上。ResizeGroup 把尺寸直接写在元素的 style.width 与 style.height 上,因此 style 请传对象:字符串形式的 style 在变化时会整体重新写入,把尺寸丢掉。
Accessibility
- 单个伸缩框每个轴上只有一个手柄是可聚焦的
separator:右侧与下侧手柄始终是;对应的右侧或下侧被enable关闭时,该轴由左侧或上侧手柄接替。名称取自语言包,分别调整宽度与高度;聚焦后按方向键,每次固定改变 10px,不受ratio、scale影响;同时按住 Alt、Ctrl 或 Meta 时不响应。鼠标右键、中键按下手柄不开始调整。 ResizeHandler是可聚焦的separator,名称同样取自语言包,传入aria-label可覆盖。沿组的方向按方向键移动它两侧的项,每次移动组尺寸的 1%,按住 Shift 为 10%;Home、End把它前一项调到最小、最大尺寸。Resizable、ResizeGroup、ResizeItem的根元素没有语义;传入aria-label或aria-labelledby时,根元素带上role="group"。
设计变量
| Sass 变量 | 值 | 用法 |
|---|---|---|
| $z-resizable_background | 20 | 伸缩框组件中背景的z-index,保持略高于handler |
| $height-row-handler | 10px | 单个伸缩框中上下handler的高度 |
| $width-col-handler | 10px | 单个伸缩框中左右handler的宽度 |
| $width-edge-handler | 20px | 单个伸缩框中边角handler的宽度 |
| $height-edge-handler | 20px | 单个伸缩框中边角handler的高度 |
| $width-horizontal-handler | 10px | 组合伸缩框中水平方向handler的宽度 |
| $height-vertical-handler | 10px | 组合伸缩框中垂直方向handler的高度 |