Skip to content

Commit

Permalink
新增文章 source/_posts/2023-09-22-stretch-border-box.md
Browse files Browse the repository at this point in the history
  • Loading branch information
rob2468 committed Sep 24, 2023
1 parent 8a48351 commit ab22acd
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 0 deletions.
117 changes: 117 additions & 0 deletions source/_posts/2023-09-22-stretch-border-box.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
---
layout: post
title: 边框可拉伸的盒子
tags:
- CSS
---

# {{ page.title }}

## 问题

前端开发中有时会遇到如下图所示的需求,即在一个不规则边框的容器中显示内容。本文讲述如何拆解这个需求,并通过 border-image CSS 属性实现。(下文讲解使用的原始图片是[这个](/images/2023-09-22-dialog.png)。)

![](/images/2023-09-22-示例.jpg)

<!-- more -->

border-image 的完整语法很复杂,不关心细节可以直接跳到 [后面的章节](#简化)

## 需求拆解

1. 准备图片。首先我们需要一张图片,图片的四个角可以是不规则的,但四条边和背景色是可以拉伸的;

2. 测量四个角需要保留的长度;

3. 测量边的宽度;

4. 尺寸调整。因为图片渲染是不会超出盒模型的,所以很有可能视觉效果上边框与内容会靠的太近,因此需要让图片渲染的超出盒模型一些。

## 方案

使用 [border-mage](https://developer.mozilla.org/zh-CN/docs/Web/CSS/border-image) CSS 属性可以实现这个需求。从 2012 年下半年开始,Chrome Android 和 Safari on iOS 都已经支持了该属性。

border-image 的值是一组 CSS 属性的缩写,分别是 [border-image-source](https://developer.mozilla.org/zh-CN/docs/Web/CSS/border-image-source) / [border-image-slice](https://developer.mozilla.org/zh-CN/docs/Web/CSS/border-image-slice) / [border-image-width](https://developer.mozilla.org/zh-CN/docs/Web/CSS/border-image-width) / [border-image-outset](https://developer.mozilla.org/zh-CN/docs/Web/CSS/border-image-outset) / [border-image-repeat](https://developer.mozilla.org/zh-CN/docs/Web/CSS/border-image-repeat)。他们各自的含义可以参考官方 MDN 文档,下面只讲述一下要点。

### border-image-slice 的作用

图片渲染的要求是四个角固定、剩余区域拉伸,border-image-slice 的作用就是将固定区域与可拉伸区域划分出来。

border-image-slice 对于图片不同区域的划分如下图所示。

![(来自 MDN 文档)](/images/2023-09-22-border-image-slice-position.png)

border-image-slice 的值相对的是图片本身的大小。

比如,`border-image-slice: 40` 划分出的区域如下图所示。(红色方块为固定区域、剩余的为可拉伸区域)

![](/images/2023-09-22-border-image-slice-demo.jpg)

### border-image-width 的作用

border-image-width 用户指定边框的宽度。比如,当 border-image-width 指定四个值时,则分别表示上右下左,含义如下图所示。

![](/images/2023-09-22-border-image-width-demo.jpg)

注意,border-image-slice 的值相对的是图片本身的大小,border-image-width 的值是 DOM 元素的大小。所以,如果 border-image-width 的值大于 border-image-slice 的值,则固定区域的图像会被拉伸;反之固定区域的图像会被压缩。

### border-image-outset 的作用

“border-image-outset 属性定义边框图像可超出边框盒的大小。”

下图示例可以看懂 border-image-outset 属性的作用。

![](/images/2023-09-22-border-image-outset-demo.jpg)

## 简化

实现本文开头所说的需求,关键点是将 border-image-slice 和 border-image-width 设置一样的值,这样保证四个角内的图像按原始大小显示。

### 代码

```
// React 组件
const StretchBorderBox = (props) => {
const { imgUrl, fixedLength, outset, content } = props;
return (
<div
className="stretch-border-box"
style={{
borderImageSource: `url(${imgUrl})`,
borderImageSlice: `${fixedLength} fill`,
borderImageWidth: `${fixedLength}px`,
borderImageOutset: outset,
borderImageRepeat: 'stretch',
}}
>
{content}
</div>
);
};
// 渲染组件
ReactDOM.render(
<StretchBorderBox imgUrl="https://mdn.alipayobjects.com/huamei_kmi0zi/afts/img/A*vLdFTYMEEzkAAAAAAAAAAAAADv17AQ/original"
fixedLength={40}
outset="19px 19px 19px 35px"
content="这个孩子会非常有名, 我们世界里的每一个人都会知道他的名字。"
/>,
document.getElementById('app')
);
```

### Demo

<iframe
width="100%"
height="300"
src="https://rob2468.github.io/mypage/stretch-border-box-demo/"
>
</iframe>
## 结语

本文讲述的是实现一个边框可拉伸盒子(比如对话框)的相关技术。

其中的一些知识点对你理解 border-image CSS 属性也有帮助。
3 changes: 3 additions & 0 deletions source/images/2023-09-22-border-image-outset-demo.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions source/images/2023-09-22-border-image-slice-demo.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions source/images/2023-09-22-border-image-slice-position.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions source/images/2023-09-22-border-image-width-demo.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions source/images/2023-09-22-dialog.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions source/images/2023-09-22-示例.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
94 changes: 94 additions & 0 deletions source/mypage/stretch-border-box-demo/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<!doctype html>
<html>

<head>
<meta charset="utf-8">
<title></title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" type="text/css" href="../global.css" />
<style>
.app {
background-color: rgba(0, 0, 0, 0.5);
padding: 50px;
display: flex;
flex-direction: column;
align-items: center;
}
</style>

<!-- babel is required in order to parse JSX -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

<!-- import react.js -->
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>

<!-- import react-dom.js -->
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"> </script>
</head>

<body>
<div id="app"></div>

<script type="text/babel">
// React 组件
const StretchBorderBox = (props) => {
const { imgUrl, fixedLength, outset, content } = props;
return (
<div
className="stretch-border-box"
style={{
borderImageSource: `url(${imgUrl})`,
borderImageSlice: `${fixedLength} fill`,
borderImageWidth: `${fixedLength}px`,
borderImageOutset: outset,
borderImageRepeat: 'stretch',
maxWidth: '50%',
wordWrap: 'break-word',
}}
>
{content}
</div>
);
};

class App extends React.Component {
constructor(props) {
super(props);
this.state = { text: '这个孩子会非常有名, 我们世界里的每一个人都会知道他的名字。' };
}
componentDidMount() {
setInterval(() => {
this.setState({
text: document.querySelector('#story').value,
});
}, 500);
}

render() {
const { text } = this.state;
return (
<div className="app">
<StretchBorderBox imgUrl="https://mdn.alipayobjects.com/huamei_kmi0zi/afts/img/A*vLdFTYMEEzkAAAAAAAAAAAAADv17AQ/original"
fixedLength={40}
outset="19px 19px 19px 35px"
content={text}
/>

<textarea id="story" name="story" rows="5" cols="33" style={{ marginTop: '50px' }} defaultValue={text}>
</textarea>
</div>
)
}
}

// 渲染
ReactDOM.render(
<App />,
document.getElementById('app')
);

</script>

</body>

</html>

0 comments on commit ab22acd

Please sign in to comment.