轻松学会:使用CSS制作可伸缩的DIV
前言
在和崽种群友聊天的时候,突然问到这么一个问题:如何实现 div 的缩放?
当即就让他自己去百度,网上此类代码太多了。
随之我们讨论到 <textarea>
标签,是自带缩放功能的,而且它的缩放好像没有直接的触发任何 JS 事件?
然后便有了2个问题
能不能不依赖 JS 实现一个可缩放的 div?
如何监听一个元素的缩放?
阅读本文,你能得到这两个问题的答案。
前言中的缩放并不严谨,多行文本编辑器自带的功能只是调整其容器大小,是 resize,而缩放指的是 scale,接下来我们会严格区分这两个概念。
认识 <textarea>
让我们线认识一下 HTML 的 <textarea>
元素,它表示一个多行纯文本编辑控件。
默认状态下,其右下角有短斜线,鼠标悬浮时会改变鼠标样式为 cursor: nwse-resize
,可以点击拖动改变文本框的大小。
如果不想要这个短斜线或不允许改变文本框大小,可以通过设置 CSS 实现
textarea {
resize: none;
}
改变 <div> 的大小
修改 textarea 的大小是通过 resize
属性实现的,我们也为 div 也加上 resize
属性。
然而,并没有生效?
仔细想想 div 和 textarea 有什么不同,好像是有个 overflow
的属性。其实如果你有过尝试的话,会发现修改 textarea 的 overflow
的属性是不生效的,始终为 scroll
现在我们知道,想要改变 div 大小,还需要一个条件:其 overflow
属性不为 visible
这样子,我们就能项文本框一样,改变 div 的大小。
div {
width: 100px;
height: 100px;
background-color: aquamarine;
resize: both;
overflow: hidden;
}
缩放 <div>
通过修改 resize
和 overflow
我们只是实现了改变容器的大小,如果想要其中的内容跟着缩放,内部的内容就需要根据比例设置宽高,方便期间,我们内部只放一张图片。
HTML
<div><img style="width: 100px; height: 100px" src="./1.jpg" /></div>
<div><img style="width: 100%; height: 100%" src="./1.jpg" /></div>
CSS
div {
width: 100px;
height: 100px;
background-color: aquamarine;
resize: both;
overflow: hidden;
margin-right: 20px;
float: left;
}
锁定纵横比
缩放不应该是任意的,我不允许帅气的路哥变成这副鬼样子!
那么又是一个难题,如何锁定纵横比?
我们想到一个解决方案,能不能只缩放水平方向,同时让盒子在垂直方向上自适应,从而实现锁定纵横比。
然后想一想,如何实现垂直方向上的自适应?
既然一层盒子无法实现,我们可以嵌套两层盒子,在改变父盒子宽度的同时,改变子盒子的高度,进而撑开父盒子,实现锁定纵横比。
有哪一个垂直方向上的 CSS 属性是与父盒子宽度有关的呢?
我猜聪明的你一定能想到 padding
,如果将 padding-bottom
与 padding-top
设置为百分比,这个百分比是根据父盒子的宽度决定的!
写出来试试
HTML
<div class="outer">
<div class="inner">
</div>
</div>
CSS
.outer {
width: 100px;
background-color: aquamarine;
resize: horizontal;
overflow: hidden;
}
.inner {
position: relative;
width: 100%;
padding-bottom: 100%;
}
效果非常完美,不管如何缩放,都是一个正方形!
为什么要选择 padding-bottom
,而不是 padding-top
或者 margin
呢?
因为我们不仅仅是造出这个盒子,还要往子盒子内部放其他元素呀。
选择 bottom
是因为我们希望容器中的 content 部分处于盒子顶部;如果使用 margin
的话子盒子的高度为 0,也就无法正常地在其内部添加元素了。
接下来把帅气的陆哥放进去
<div class="outer">
<div class="inner">
<img style="width: 100%; height: 100%" src="./1.jpg" />
</div>
</div>
欸?把盒子挤下来了。让我们为其设置一个绝对定位,就不会把父盒子撑开了。
<div class="outer">
<div class="inner">
<img style="width: 100%; height: 100%; position: absolute" src="./1.jpg" />
</div>
</div>
完美实现锁定纵横比的缩放~
新 CSS 属性 aspect-ratio
其实呢,刚刚费那么大劲地去锁定纵横比,但是 CSS 已经提供了这样一个属性 aspect-ratio
,这个属性可以直接为容器设置纵横比。
使用这个属性,就不再需要两层的盒子嵌套,下面的代码就能实现同样的效果。
HTML
<div class="container">
<img style="width: 100%; height: 100%" src="./1.jpg" />
</div>
CSS
.container {
width: 100px;
background-color: aquamarine;
resize: horizontal;
overflow: hidden;
aspect-ratio: 1 / 1; /* 锁定纵横比 */
}
但这个属性的兼容性并不好,只有最新版本的浏览器才能够支持。
元素尺寸监听器 ResizeObserver
盒子虽然造好了,但内部的元素想要跟着一起缩放,需要全部以百分比的形式写样式,这显然是非常不友好的,更别说还有很多 CSS 属性比如 border
没法使用百分比。
然后我们想到了相对长度单位 em
,它是根据父元素的 font-size
属性计算的。
如果我们在缩放的同时改变父盒子的字体大小,子盒子中元素根据 em
设置宽度,自适应的问题就解决了。
很遗憾,CSS 已经到头了,想要实时修改父盒子的字体大小,只能依赖于 JS 了。
那么问题来了,我们在缩放盒子的时候,触发了哪些事件呢?
大家肯定都能想到:鼠标按下、拖动、松开。传统的使用 JS 实现修改盒子大小的方式,就是通过这3个事件实现的。
不过我们将采取另一种更为简单的方式:ResizeObserver
ResizeObserver
接口可以监听到元素的内容区域或边界框的改变。
在这里简单介绍一下用法,详情请看 ResizeObserver 与 ResizeObserverEntry
ResizeObserver()
是一个构造器,接受一个回调函数并返回一个 resizeObserver
对象。
resizeObserver
对象具有 observe
与 unobserve
方法,用于开始/结束对 DOM 元素的监听。
传递给ResizeObserver()
构造器的回调函数会接受一个参数,是由 ResizeObserverEntry
对象组成的数组。在 ResizeObserverEntry
对象身上,包含着 DOM 元素的尺寸信息。
最终代码如下: HTML
<div class="container">
<img style="width: 5em; height: 5em" src="./1.jpg" />
</div>
CSS
.container {
width: 100px; /* 共5em */
font-size: 20px;
background-color: aquamarine;
resize: horizontal;
overflow: hidden;
aspect-ratio: 1 / 1; /* 锁定纵横比 */
}
img {
width: 5em;
height: 5em;
}
JS
const resizeObserver = new ResizeObserver((entryList) => {
// 获取元素与尺寸信息
const { target, contentRect } = entryList[0]
target.style.fontSize = contentRect.width / 5 + 'px'
})
const container = document.querySelector('.container')
resizeObserver.observe(container) // 开始监听
存在的问题
因为我们设置的是 resize: horizontal
,所以鼠标指针悬浮的双箭头是水平方向的,与实际并不相符,浏览器没有暴露出设置这个角标样式的伪类元素,作者没有找到能够解决此问题的方法。
结语
如果文中有错误或不严谨的地方,请务必给予指正,十分感谢。
如果喜欢或者有所启发,欢迎点赞关注,鼓励一下新人作者。