场景一:自动切分子页
要求
根据页面容器大小自动装填对话内容。当屏幕中仅能显示一段对话还过大时,缩小字号以适应容器大小。
实现原理
使用一个隐藏的内容区域元素作为虚拟容器去丈量内容渲染所需要的实际空间,然后通过得到的实际空间去计算分页。
父组件:
<template>
<div
id="view-container"
class="slide-page-container swiper-wrapper"
>
<!-- 虚拟容器 -->
<virtual-content
:index="index"
:sentences="sentences"
/>
<!-- 实际内容 -->
<div
v-for="(sentenceInfo, sliderIndex) of list"
:key="sliderIndex"
:class="[
'slide-page-content',
'swiper-slide',
`swiper-slide-${sliderIndex}`
]"
>
<p
v-for="(item,pIndex) of sentenceInfo"
:key="pIndex"
class="reality-content"
:style="getPStyle(item,sliderIndex, pIndex)"
>
...
</div>
</div>
...
</template>
虚拟元素组件:
<template>
<div
:id="`nodes-parent-${index}`"
class="slide-page-all"
>
<p
class="virtual-content"
v-for="(sentence,sententceIndex) of sentences"
:key="sententceIndex"
>
<br v-if="sentence.empty">
<span
v-else
class="word"
>{{ sentence.englishSubtitle}}</span>
</p>
</div>
</template>
<style lang="less" scoped>
.slide-page-all{
visibility: hidden;
position:absolute;
top:0;
left:0;
right: 0;
.virtual-content{
font-size: 20px;
line-height: 1.5;
}
}
</style>
注意: 为了保证虚拟容器在实际目标容器前加载,可以将虚拟容器作为和目标容器同级的子组件。
分页计算
// 增加索引
const addIndexToValue = arr => {
return arr.map((v, i) => {
return {
index: i,
height: v
};
});
};
/** 分页算法【重点】
* arr: 每一段内容所占的实际高度的集合
* max: 目标容器的最大高度
* /
const getPages = (arr, max) => {
if (max <= 0 ) {
return [];
}
const result = [];
const data = addIndexToValue(arr);
let pre = 0;
let tmp = [];
for (let i = 0, len = data.length; i < len; i++) {
const current = pre + data[i].height;
if (current >= max) {
if (tmp.length > 0) {
result.push(tmp);
tmp = [{ ... (data[i] || {}), currentIndex: 0}];
pre = data[i].height;
if (i === data.length - 1) {
result.push(tmp);
}
} else {
result.push([{ ... (data[i] || {}), currentIndex: 0}]);
tmp = [];
}
} else {
tmp.push({ ...(data[i] || {}), currentIndex: tmp.length});
pre = pre + data[i].height;
if (i === data.length - 1) {
result.push(tmp);
}
}
}
return result;
};
// 缓冲值15
const OFFSET = 15;
// 在页面中的实际应用
export const getComputedNode = index => {
const wrap = document.getElementById('view-container');
if (!wrap) {
return [];
}
const parent = document.getElementById(`nodes-parent-${index}`);
const wrapHeight = wrap.clientHeight;
const children = Array.from(parent.children);
const arr = [];
children.reduce((pre, node) => {
const height = node.clientHeight;
pre.push(height);
return pre;
}, arr);
const result = getPages(arr, wrapHeight - OFFSET);
return result;
};
字体缩小判断
// font-size
public getResizeStyle(sliderIndex, pIndex) {
const swiperPages = this.list[sliderIndex];
if (swiperPages.length === 1) {
// 只有一句话的时候
const { height } = this.list[sliderIndex][pIndex]; // height:自适应时预设的高度
const parentHeight = document.getElementById('view-container').clientHeight; // parentHeight:实际父容器高度
const wordEl = document.querySelector('.virtual-content .word');
if (!wordEl) {
// 元素还未渲染的时候
return '';
}
const fontSizeStr = window.getComputedStyle(wordEl)['font-size'] || '';
const fontSize = parseFloat(fontSizeStr);
// 当预设的高度大于实际父容器高度时,字体要缩小以适应显示
if ( height > parentHeight) {
return `font-size: ${Math.floor(fontSize * parentHeight / height)}px`;
}
return '';
} else {
return '';
}
}
扩展
以后遇到需要自适应的,都可以使用虚拟元素的方式去测量然后计算适应。
场景二:每排三个卡片适应
要求
3个一排,小于3个从左往右依次排列。
实现原理
- 每行固定放三个元素,每个元素高度固定,水平和垂直方向间距固定,宽度自适应
- 为了很好支持
justify-content:space-between
,需要处理sum % 3
为2
的情况:在数据上塞入一个空元素
.content{
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
场景三:内容左,整体居中
要求
在长度长短不一的段落中要保持所有段落的内容居左显示,段落外的一层容器在页面中间显示。
实现原理
- 布局分为三个层级:文字段落、文字段落容器、篇章容器
为了实现文字内容在水平方向上的达到上限再换行,文字段落容器采用
display:inline-block
,让文字内容撑满水平方向后自动换行。文字段落容器text-align:left
实现左对齐.pcontainer{ display:inline-block; text-align:left }
篇章容器实现内容居中对齐即可
.chapter{ display:flex; justify-content: center; }
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。