绪论
背景
大屏可视化也许是目前的领导们最热衷的一种前端开发方向,其突出的特点就是字体大、数据多、内容密集。但在前端的日常开发过程中,大屏的适配往往是一个难点。在设计过程中,大屏通常只会优先兼容一个分辨率,但是产品、领导和甲方,则会使用分辨率不同的平板、笔记本甚至手机浏览大屏页面,而又因为大屏本身的内容密度高的特点,导致传统的兼容模式很难适应前端对界面的需求(即内容容易挤在一起)。因此,本文描述了一个前端可视化的通用解决方案以快速适配各种分辨率。
灵感
在图片和视频的适配中,object-fit CSS 属性指定可替换元素的内容应该如何适应到其使用的高度和宽度确定的框,如果你不知道的话,可以看看 MDN。当 object-fit 属性的取值为 “contain” 时,被替换的内容将被缩放,以在填充元素的内容框时保持其宽高比。 整个对象在填充盒子的同时保留其长宽比,因此如果宽高比与框的宽高比不匹配,该对象将被添加“黑边(广义的)”。
Contain 组件
原理
与 object-fit: contain 属性一致,Contain 组件实现了一种行为——通过对外部容器大小的监听,保持内部组件以明确的比例展示内容。
监听外部容器大小的改变,可以使用 ResizeObserver API(>= chrome64,通过 Polyfill 可以兼容到 ie8)。保持内部容器的比例,可以使用 CSS 的 transform 的 scale 属性(>= ie10)。
尝试写一个 Vue3 版本的 Contain 组件
Props
要保持内部组件的比例,通常需要使用者输入一个明确的比例,因此,组件接受 width 和 height 属性。
export const containProps = {
width: { type: Number, default: 1920 },
height: { type: Number, default: 1080 }
}
Setup
来到组件的主体,一是需要监听外部容器的大小,二是计算内部容器的正确缩放比例。这里使用了 vueuse 的 useElementSize 方法,其原理是 ResizeObserver,但是使用封装好的 API 可以方便很多。
import { ref, computed, defineComponent } from 'vue'
import { useElementSize } from 'vueuse'
export default defineComponent({
// ...
props: containProps,
setup(props) {
const containRef = ref<HTMLDivElement>()
const { width, height } = useElementSize(containRef)
const scale = computed(() => Math.min(width.value / props.width, height.value / props.height))
return {
containRef,
scale
}
},
// ...
})
Render
共需要渲染两个元素,父级元素和内部保持比例的元素,其他元素由使用者通过插槽传入。
export default defineComponent({
// ...
render() {
return (
<div ref="containRef" class="contain">
<div
class="contain-content"
style={{
width: this.width + 'px',
height: this.height + 'px',
transform: `scale(${this.scale})`
}}
>
{ this.$slots.default?.() }
</div>
</div>
)
}
})
Style
Contain 组件会始终保持内部内容垂直居中的状态,flex 布局是一个很方便的选择。
.contain {
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
&-content {
flex: 0 0 auto;
position: relative;
}
}
Demo
可以使用电脑查看 容纳 Contain。
已知问题
显而易见的,Contain 组件的问题有二
- 如果外部容器比例与内容容器比例设定不一致,必然会出现“黑边”;
- 它仍然解决不了过小的设备观看大屏造成的字体过小的问题,比如手机(虽然拿手机看大屏这个行为非常让人困惑)。
总之,至少前端可以愉快的在组件内直接使用 UI 提供的 px 而不用考虑页面比例的问题了。