scale
我们现在的目标是,在子容器的边框顶住父容器边框的同时,让子容器内的内容也随之缩放。较为典型的应用场景为,将一张较大的图像最大化地按比率缩放进父容器中。
这里使用CSS的transform技术来解决此问题。
先解决布局的问题。
body {
height: 100vh;
}
div {
width: 100px;
height: 300px;
transform: scale(0.5);
transform-origin: 0 0;
box-sizing: border-box;
border: 1px solid green;
background-color: #555;
}
默认情况下,子标签的高度值缩小后,body的高度值不会自动缩小。
将body的height属性值设置为100vh,可在子标签应用缩放后,其尺寸自动调整回到整个客户端区域内。
上面代码的效果,子容器的高度值超出了客户端高度值,手工应用缩放后,整个子容器正好顶住父容器,同时不破坏父容器原来的尺寸大小。
下节解决如何自动计算缩放因子的问题。
calc
CSS的calc函数虽然许多时候很有用,但在涉及本例的应用中,却主要存在着以下限制:
- calc函数的除数不能带有单位
- 标签的width及height等属性值必须带有单位
- scale函数的参数不能带有单位
先解决第1及第2个问题。
body {
height: 100vh;
}
:root {
--w-value: calc(100 * 3);
--h-value: calc(100 * 2);
--scale-ratio: calc(var(--h-value) / var(--w-value));
--div-width: calc(var(--w-value) * 1px);
--div-height: calc(var(--h-value) * 1px);
}
div {
width: var(--div-width);
height: var(--div-height);
transform: scale(var(--scale-ratio));
transform-origin: 0 0;
box-sizing: border-box;
border: 1px solid green;
background-color: #555;
}
不带单位的值用于计算缩放因子,带单位的值用于设置标签的尺寸大小。
第二步,尝试解决解决第3个问题。
设置Y轴上的缩放因子时,客户端高度为150px,则可使用以下代码150 / var(--h-value)
:
body {
height: 100vh;
}
:root {
--w-value: calc(100 * 3);
--h-value: calc(100 * 2);
--scale-ratio: calc(150 / var(--h-value));
--div-width: calc(var(--w-value) * 1px);
--div-height: calc(var(--h-value) * 1px);
}
div {
width: var(--div-width);
height: var(--div-height);
transform: scale(var(--scale-ratio));
transform-origin: 0 0;
box-sizing: border-box;
border: 1px solid green;
background-color: #555;
}
但若使用100vh / var(--h-value)
则不行:scale函数的参数不能带有单位。
Chrome在这里就卡住了。
鉴于上述比较复杂的规则,这里最灵活的方法就是请JavaScript来帮忙了。
body {
width: 100vw;
height: 100vh;
}
div {
width: calc(100px * 3);
height: calc(100px * 2);
box-sizing: border-box;
border: 1px solid green;
background-color: #3F3F3F;
}
const body = document.body;
const div = document.querySelector('div');
let bodyWidth = getComputedStyle(body).getPropertyValue('width');
let bodyHeight = getComputedStyle(body).getPropertyValue('height');
let divWidth = getComputedStyle(div).getPropertyValue('width');
let divHeight = getComputedStyle(div).getPropertyValue('Height');
let xScale = parseFloat(bodyWidth) / parseFloat(divWidth);
let yScale = parseFloat(bodyHeight) / parseFloat(divHeight);
let scaleFactor = Math.min(xScale, yScale);
div.style.setProperty('transform', `scale(${scaleFactor})`);
div.style.setProperty('transform-origin', '0 0');
先分别计算出X轴与Y轴上的缩放因子,取其最小值,再为div动态添加变换属性。
修改div的width及height属性值,该标签总能最大化地按比例缩放。
object-fit
object-fit可像SVG的viewBox一样,指定是否按比例缩放。
#container {
margin: 0.2em;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1em;
& div {
border: .1px solid gray;
border-radius: 0.5em;
text-align: center;
& img {
width: 100px;
aspect-ratio: 2 / 1;
border: 1px solid gray;
filter: brightness(80%);
background-color: #555;
}
& p {
color: thistle;
margin: 0.2em auto;
}
}
}
let fitTypes = ['fill', 'contain', 'cover', 'none', 'scale-down'];
function createImgs() {
let target = document.querySelector('#container');
for (let fitType of fitTypes) {
let img = document.createElement('img');
img.src = "/js/esm/three/manual/examples/resources/images/happyface.png";
img.style.objectFit = fitType;
let p = document.createElement('p');
p.textContent = fitType;
let div = document.createElement('div');
div.appendChild(img);
div.appendChild(p);
target.appendChild(div);
}
}
createImgs();
只有none不缩放,其余值都会缩放。
在缩放的值中:
- 只有fill不按比例缩放,其只是简单的边角定位。
- 在按比例缩放的值中:
- cover取宽高的最大值按比例缩放,超出范围部分被裁剪。
- contain按比率缩放进盒子中,即其内容将完全显示在盒子中。
- scale-down
- 如果图片比盒子小,不缩放,效果等同于none;
- 如果图片比盒子大,则向下按比例缩放,效果等同于contain。
object-position
上节的contain及scale-down在盒子中有留白,可使用object-position来定位X轴及Y轴的偏移值。
img {
border: 1px solid gray;
filter: brightness(80%);
background-color: #555;
object-fit: contain;
}
#horz img {
width: 100px;
aspect-ratio: 2 / 1;
&:nth-of-type(1) {object-position: 0 0;}
&:nth-of-type(2) {object-position: 50% 0;}
&:nth-of-type(3) {object-position: 100% 0;}
}
#vert img {
height: 100px;
aspect-ratio: 1 / 2;
&:nth-of-type(1) {object-position: 0 0;}
&:nth-of-type(2) {object-position: 0 50%;}
&:nth-of-type(3) {object-position: 0 100%;}
}
#container {
& div {
margin: 0.5em;
padding: 0.5em;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
justify-items: center;
border: .1px solid gray;
border-radius: 0.5em;
& p {
color: thistle;
margin: 0.1em auto;
grid-row: 2;
}
}
}
let fitTypes = ['fill', 'contain', 'cover', 'none', 'scale-down'];
function initImgs() {
let imgs = document.querySelectorAll('img');
imgs.forEach(img => {
img.src = `/js/esm/three/manual/examples/resources/images/happyface.png`;
});
}
initImgs();
从效果上看,类似于使用background-image设置背景图像,但这种方式可以直接设置img的样式。