携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情
背景
目前所在公司主做大屏可视化项目(智慧楼宇、智慧园区等等方向),之前是要求按照客户的屏幕尺寸写死即可,但是最近做一个公司自己的用于对外宣传的可视化demo,领导要求必须要适配笔记本。既然是demo的话,而且主要以笔记本来进行展示,那么这边就和UI商量,设计稿以1920X1080的尺寸来进行设计,前端按照这个尺寸进行开发
问题
在适配的过程中,遇到最大的难点就是windows系统自带缩放,导致页面不能适配的问题。windows系统自带缩放会影响到浏览器视口的宽高变化,其本质还是要去适配任何分辨率
探究方案
rem 适配scale适配zoom 适配
解决历程
rem布局
利用 postcss-pxtorem 插件,在webpack打包的时候,将css中的px自动转成rem。
具体实现
-
安装
yarn add postcss postcss-loader postcss-pxtorem -D -
配置
- 项目根目录下创建postcss.config.js文件
module.exports = {
plugins: [
[
'postcss-preset-env',
{
// autoprefixer: {
// grid: true,
// },
},
],
[
'postcss-pxtorem',
{
rootValue: 100,
propList: ['*'],
},
],
],
};
- 修改webpack配置,编译sass/less的时候,新增postcss-loader,注意:postcss-loader需要配置在css-loader之前执行
{
loader: require.resolve('postcss-loader'),
},
- 编写动态修改html标签fontSize的大小
var htmlDOM = document.getElementsByTagName('html')[0];
var designWidth = 1920 // 设计稿的宽度 --- 已知
var designHtmlFontSize = 100; // 设计稿上面的html font-size的大小 --- 已知
var actualHtmlFontSize; // 实际html font-size的大小 --- 未知
function setRootFontSize() {
var viewportWidth = document.documentElement.clientWidth || document.body.clientWidth; // 实际浏览器视口的宽度 ---- 已知
actualHtmlFontSize = viewportWidth * designHtmlFontSize / designWidth; // 3个已知条件,1个未知条件
htmlDOM.style.fontSize = actualHtmlFontSize + 'px';
}
setRootFontSize()
window.addEventListener("resize", setRootFontSize)
总结
优势: 自适应流畅,实时根据浏览器视口的宽度变化而适应。开发者书写样式的时候,无需考虑单位转换问题。
劣势:会受到浏览器fontSize最小为12px的影响,同时也会受到windows系统自带的缩放影响,根据实际使用来看rem布局在web端只适配不同分辨率但是同一宽高比,并且windows系统缩放为100% 的情况。
所以在web端采用rem布局不太合适,移动端采用rem布局问题是不大的。
scale缩放
利用css 3提供的transform: scale属性进行页面的缩放
具体实现
通过监测window身上的resize事件,在resize事件中,实时的获取浏览器视口的宽和高,用视口的宽、高度与UI设计稿的宽、高度进行比较,得出实际的zoom的值。
useEffect(() => {
calculateCssScale();
window.addEventListener('resize', calculateCssScale);
return () => {
window.removeEventListener('resize', calculateCssScale);
};
}, []);
/**
* 计算CSS scale -- 保持UI设计稿比例的缩放 页面不会出现压缩情况
*/
const calculateCssScaleKeepRate = () => {
const uiWidth = 1920;
const uiHeight = 1080;
const wrapperDOM: HTMLDivElement = document.querySelector('#ice-container')!;
wrapperDOM.style.width = `${uiWidth}px`;
wrapperDOM.style.height = `${uiHeight}px`;
const viewportHeight: number = document.documentElement.clientHeight || document.body.clientHeight; // 实际浏览器视口的高度
const viewportWidth: number = document.documentElement.clientWidth || document.body.clientWidth; // 实际浏览器视口的宽度
const scale =
viewportWidth / viewportHeight < uiWidth / uiHeight ? viewportWidth / uiWidth : viewportHeight / uiHeight;
wrapperDOM.style.transform = `scale(${scale})`;
wrapperDOM.style.transformOrigin = 'left top';
if (viewportWidth / viewportHeight < uiWidth / uiHeight) {
const margin = viewportHeight - wrapperDOM.getBoundingClientRect().height;
wrapperDOM.style.margin = `${margin / 2}px 0`;
} else {
const margin = viewportWidth - wrapperDOM.getBoundingClientRect().width;
wrapperDOM.style.margin = `0 ${margin / 2}px`;
}
};
/**
* 计算CSS scale --- 不保持比例 会压缩页面
*/
const calculateCssScale = () => {
const uiWidth = 1920;
const uiHeight = 1080;
const bodyDOM = document.getElementsByTagName('body')[0];
const gaodeDOM = document.querySelector('#gaode-map');
const scaleX = window.innerWidth / uiWidth;
const scaleY = window.innerHeight / uiHeight;
// const offsetX = (window.innerWidth - uiWidth) / (2 * scaleX);
// const offsetY = (window.innerHeight - uiHeight) / (2 * scaleX);
bodyDOM.style.width = `${uiWidth}px`;
bodyDOM.style.height = `${uiHeight}px`;
// bodyDOM.style.transform = `scale(${scaleX},${scaleX}) translate(${offsetX}px,${offsetY}px)`;
bodyDOM.style.transform = `scale(${scaleX},${scaleY})`;
bodyDOM.style.transformOrigin = 'left top';
};
总结
优势:
- 兼容性更好
- 不会引起回流,只会发生重绘
劣势:
scale缩放有两种方式,一种是缩放不保持UI设计稿比例缩放,另一种是保持UI设计稿比例缩放
不保持UI设计稿比例缩放
- 对于同一比例的分辨率适配,对于不同比例的分辨率,scale缩放会直接导致页面有压缩。
- 对于antd组件库中的Select的弹出层位置也会出现很大偏差
- 如果地图是高德地图或者其他GIS地图的话,会导致鼠标点击地图的点有位置偏差, 此种情况下,采用对地图再次应用scale,地图的scale值与body身上的scale值相乘等于1即可
保持UI设计稿比例缩放
- 如果地图是高德地图或者其他GIS地图的话,会导致鼠标点击地图的点有位置偏差。(目前没找到完美解决方案)
- 对于antd组件库中的Select的弹出层位置也会出现很大偏差
zoom缩放
介绍
利用css3 提供的zoom属性进行页面的缩放。zoom缩放,默认是从元素的左上角进行缩放,被缩放的元素,元素本身的大小会发生改变,此行为会直接引起文档流的回流与重绘。
具体实现
通过监测window身上的resize事件,在resize事件中,实时的获取浏览器视口的宽和高,用视口的宽、高度与UI设计稿的宽、高度进行比较,得出实际的zoom的值。
react 项目案例
const [commonState, commonDispatchers] = store.useModel('commonModel');
useEffect(() => {
calculateCssZoom();
window.addEventListener('resize', calculateCssZoom);
return () => {
window.removeEventListener('resize', calculateCssZoom);
};
}, []);
/**
* 计算CSS zoom
*/
const calculateCssZoom = () => {
if (domRef.current) {
const viewportHeight: number = document.documentElement.clientHeight || document.body.clientHeight; // 实际浏览器视口的高度
const viewportWidth: number = document.documentElement.clientWidth || document.body.clientWidth; // 实际浏览器视口的宽度
const designHeight: number = 1080; // 设计稿的高度
const designWidth: number = 1920; // 设计稿的宽度
const zoomValueHeight = domHeight / designHeight; // 高度计算出来的zoom值
const zoomValueWidth = domWidth / designWidth; // 宽度计算出来的zoom值
const targetZoom = zoomValueWidth > zoomValue ? zoomValue : zoomValueWidth; // 比较大小选择最终实际需要的zoom值
// 拿到最终的zoom值之后,可以选择直接应用到body标签身上,或者将zoom值储存在store里面,分发到需要被适配的组件里面使用,笔者这里采用的是储存store向下分发的方案。(根据实际情况选择)
commonDispatchers.setState({
cssZoom: targetZoom,
});
}
};
return (
<div style={{zoom: commonState.cssZoom}} >大屏内容</div>
)
总结
优势:
- 几乎完美适配各种宽高比的分辨率(故解决了windows系统自带的缩放比导致页面适配问题)
- 没有两边或者上下留白的情况
- 不受浏览器最小12px的影响
劣势:、
- 会引起回流和重绘,导致性能要比scale方式差
- 兼容性差
最终我选择zoom缩放方案
我最终选择的是zoom的方式进行适配。理由如下:
- 大屏可视化平台用户量少,基本上属于只在一台电脑上进行显示,所以我们可以直接推荐客户用chorme浏览器去打开网页。以此来解决zoom的兼容性问题。
- scale其实是等比例缩放比较好,非等比例会出现上下 或者 左右留白的情况,如果非要完全充满浏览器视口,则会有很明显的页面被压缩的情况。, scale 缩放还有一些问题到现在没找到完美解决方案。
- 虽然zoom的性能比scale差,但是对于B端来说这点性能微不足道,更何况还是大屏可视化平台。
- 淘汰rem的理由就一个:它受浏览器字体最小12px的影响。
适配的过程中遇到的问题&解决方案
问题1:
zoom适配过程中,如果是百度或者高德地图或者GIS场景这种作为底图的话,会出现鼠标点击地图有位置偏差。
解决方案:
不对 地图/GIS 进行zoom,只对我们自己写的页面或组件的最外层div添加zoom值。高德地图只需要始终充满整个容器
问题2:
zoom适配过程中,遇到类似于Antd 的Select的弹出层,位置不正确
解决方案:
这是因为Antd组件库Select的弹出层默认是直接插在body标签下面的,如果我们的zoom只应用在我们自己写的组件外层div上面的话,那么需要将Select的弹出层挂载到Select组件的父节点身上即可, 这样做的目的是让弹出层也要被zoom。Select组件有个getPopupContainer可以处理这个情况
问题3:
zoom 适配过程中,遇到了鼠标悬浮至echarts图表上面的时候,位置出现偏移
解决方案:
- echarts被zoom之后,我们要把echarts 再zoom回去。最开始给页面设置了多少zoom,就要单独给echarts设置了
1 / zoom的值, 这样操作下来相当于echarts没有被zoom。 - 此时我们选择scale对echarts进行缩放,给echarts设置
transform: scale(zoom的值)&transform-origin: left top,这样就解决了echarts鼠标悬浮或者点击的时候有位置偏差的情况了 - 可以对echarts进行一次封装,专门处理这个情况,详见github上源代码里面的src/components/YjEcharts组件
问题4
scale 缩放 不保持UI设计稿比例的适配时,果是百度或者高德地图或者GIS场景这种作为底图的话,会出现鼠标点击地图有位置偏差。
解决方案
底图最外层div再次设置scale的值,使得地图最外层div与顶层div设置scale的值的乘积是1即可。假如页面缩放scale的值是0.5的话,那么地图的最外层div要设置scale为 1 / 0.5 = 2
尾声
对于scale缩放页面存在的问题,后续会继续探究,如果能解决会继续更新此文章