前言
虽然现在可能响应式布局多一些,但是我还是觉得自适应单位比较好用。对于一些框架的UI组件,例如Vuetify,可能并不支持使用自适应单位,此时就会非常影响布局效果。
这篇文章提供的方法可以帮助你实现这个功能,但是这个方法更适合实现需要在不同屏幕方向下使用不同长度单位的需求。
仅对于实现vw/vh来说,这不是非常完美的方法。
问题
首先,vw/vh就是基于当前视窗的百分比,这个我就不再阐述了。
我们来看一下环境:
<v-progress-circular
color="primary"
indeterminate
size="30vw"
width="3"
>
</v-progress-circular>
<!-- 此时获取为30,默认单位为px,使用的是30px而不是30vw -->
我们换个思路,既然不支持,因为自适应环境又很难获取到目标环境的具体像素值,那我们可以逆向工程实现一个动态计算的单位。
思路
我们通过JS捕获到用户的屏幕宽度,高度以及其他你想要使用的尺寸,对这个数据按照百分比计算就可以实现类似vw/vh的效果。
我会使用MQL来监听屏幕方向,下面我将解释原因。
当然,按照这个思路,做点别的也可以,比如获取一下其他的数据,宽度等等。
实现
首先我们需要获取用户屏幕的宽度和高度。
因为BOM里相关的数据非常多,我在这里推荐一篇文章:
关于js获取屏幕高度和宽度( window.document.body,window.screen)(PC端和移动端)blog.csdn.net
我最后选择的是document.body.clientWidth/Height。
按照我的测试,虽然在旋转屏幕时两个数据会互换,也就是clientWidth是实际屏幕方向下的宽度,但是这个数据转换并没有引起Vue的第二次计算。我目前的解决方案是使用MQL的监听器,应该会有更好的方案。
虽然这个方法有点曲线救国的意思,但是实现并不是非常复杂,所以我保留了这个方法。
如果你正在处理的是一个小程序或者其他的内容,在限制屏幕方向的情况下,可以不考虑监听,跳过这一步。
this.size = document.body.clientWidth * 0.4;
我们使用 MediaQueryList 来捕获用户屏幕方向。
MediaQueryListdeveloper.mozilla.org
我大概解释一下,这个接口可以为我们提供相关的信息,如果你感兴趣可以仔细看一下我们获取的MQL对象内容。
MQL有点复杂,如果你想知道更多有关MQL的内容可以阅读MDN文档。
我们可以利用生命周期钩子实现这个功能,在这里我选择的是created。
created() {
let mql = window.matchMedia("(orientation: portrait)");
同时,MQL会自带一个matches属性,当其值为真时即表明此时的屏幕方向为portait,即竖屏状态,你也可以将这个参数修改为landscape,即横屏。同时,我们可以使用MQL的addListener来订阅事件,监听屏幕方向变化。
我们并不需要在屏幕不同方向时做出不同的操作,所以我们可以省略MDN示例中的判断。当然,你也可以实现在不同屏幕方向(不同屏幕比例)时更换单位,但是因为我使用的Vuetify是有断点属性的,所以这里对我没有什么帮助。
function handleOrientationChange(mql) {
// 干点什么
}
mql.addListener(handleOrientationChange);
这样,我们整体的结构就基本上完成了。
我们可以把其带入到之前的监听器里:
// 监听
// eslint-disable-next-line no-unused-vars
function handleOrientationChange(mql) {
size = document.body.clientWidth * 0.4;
}
因为我开启了ESLint,所以增加了注释。如果你没有开启ESLint,可以放弃这种写法。
这里放弃ESLint是为了减少代码量,否则的话就需要一个判断但是执行两个完全相同的语句。
如果你想在不同屏幕方向时使用不同的尺寸,则可以保留判断语句:
我引用MDN的例子:
var para = document.querySelector('p');
var mql = window.matchMedia('(max-width: 600px)');
function screenTest(e) {
if (e.matches) {
/* the viewport is 600 pixels wide or less */
para.textContent = 'This is a narrow screen — less than 600px wide.';
document.body.style.backgroundColor = 'red';
} else {
/* the viewport is more than than 600 pixels wide */
para.textContent = 'This is a wide screen — more than 600px wide.';
document.body.style.backgroundColor = 'blue';
}
}
mql.addEventListener('change', screenTest);
我们回到template,依靠Vue的双向绑定,实现数据自动更新:
<v-progress-circular
color="primary"
indeterminate
:size="size"
width="3"
>
</v-progress-circular>
data: () => ({
size: null
}),
因为我们的size初始值为null,为了不让在渲染时出问题,我们可以在created中填充一个默认值。我这里选择的是0.4,类似40vw。
同时,由于作用域,在一个函数内部使用this并不能访问到data中的数据,所以使用一个简单的声明解决这个小问题。
对应的,修改钩子:
created() {
let mql = window.matchMedia("(orientation: portrait)");
let __this = this;
// mql.matches为true时为竖屏
this.size = document.body.clientWidth * 0.4;
// 监听
// eslint-disable-next-line no-unused-vars
function handleOrientationChange(mql) {
__this.size = document.body.clientWidth * 0.4;
}
mql.addListener(handleOrientationChange);
}
效果

Demo: ldwid.com
虽然我实装了这个属性,但是我没有对我的站点做更多的移动端横屏适配。
最后
感谢:
JS 获取和监听屏幕方向变化(portrait / landscape)www.cnblogs.com
继续给我自己打广告,有校招或者内推名额请联系我!我会很努力的!