前端多视图布局组件封装

129 阅读5分钟

多视图布局组件封装

一、需求

在平时开发中会遇到页面上多个视图区域,每个区域展示不同的内容,并且视图的排布可以动态的调整(如:上下平分切换为左右平分,在切换为上二下一布局等)。
如果只有很少的布局情况可以直接使用样式写死即可,如果是多种布局情况,就无法满足灵活的布局需求。

二、思路

因为视图布局可以灵活的切换,所以我们就不能将视图位置写死,而是要根据不同的场景灵活的计算出当前出每个视图在页面中的位置,而想要计算出视图的位置,就需要有各个视图的数据,最后在根据这些数据将视图的渲染出来。

经过计算和实践整理步骤如下:
1、定义视图的渲染数据
2、根据定义的数据渲染视图
3、根据传入的视图数据计算最终样式并抽取成组件

三、代码

1、定义视图的渲染数据
首先我们定义不同布局下的视图数据,布局样式使用theme1 … 来表示,将屏幕分成n行和n列分别用 row和column表示,然后定义当前视图的位置数据(左 left、上 top )和大小(宽 width 、高 height),n个视图定义n组数据。完整数据如下:

const viewerLayout = {
  // 单视图
  theme1: {
    label: '单视图',
    layout: { row: 1, column: 1 },
    grids: [
      { left: 0, top: 0, width: 1, height: 1 },
    ],
  },
  // 左右双视图
  theme2: {
    label: '左右',
    layout: { row: 1, column: 2 },
    grids: [
      { left: 0, top: 0, width: 1, height: 1 },
      { left: 1, top: 0, width: 1, height: 1 },
    ],
  },
  // 上下双视图
  theme3: {
    label: '上下',
    layout: { row: 2, column: 1 },
    grids: [
      { left: 0, top: 0, width: 1, height: 1 },
      { left: 0, top: 1, width: 1, height: 1 },
    ],
  },
  // 左一右二三视图
  theme4: {
    label: '左一右二',
    layout: { row: 2, column: 2 },
    grids: [
      { left: 0, top: 0, width: 1, height: 2 },
      { left: 1, top: 0, width: 1, height: 1 },
      { left: 1, top: 1, width: 1, height: 1 },
    ],
  },
  // 左二右一三视图
  theme5: {
    label: '左二右一',
    layout: { row: 2, column: 2 },
    grids: [
      { left: 0, top: 0, width: 1, height: 1 },
      { left: 0, top: 1, width: 1, height: 1 },
      { left: 1, top: 0, width: 1, height: 2 },
    ],
  },
  // 上一下二三视图
  theme6: {
    label: '上一下二',
    layout: { row: 2, column: 2 },
    grids: [
      { left: 0, top: 0, width: 2, height: 1 },
      { left: 0, top: 1, width: 1, height: 1 },
      { left: 1, top: 1, width: 1, height: 1 },
    ],
  },
  // 上二下一三视图
  theme7: {
    label: '上二下一',
    layout: { row: 2, column: 2 },
    grids: [
      { left: 0, top: 0, width: 1, height: 1 },
      { left: 1, top: 0, width: 1, height: 1 },
      { left: 0, top: 1, width: 2, height: 1 },
    ],
  },
  // 田字四视图
  theme8: {
    label: '上二下二',
    layout: { row: 2, column: 2 },
    grids: [
      { left: 0, top: 0, width: 1, height: 1 },
      { left: 1, top: 0, width: 1, height: 1 },
      { left: 0, top: 1, width: 1, height: 1 },
      { left: 1, top: 1, width: 1, height: 1 },
    ],
  },

  // 左一右三
  theme9: {
    label: '左一右三',
    layout: { row: 3, column: 2 },
    grids: [
      { left: 0, top: 0, width: 1, height: 3 },
      { left: 1, top: 0, width: 1, height: 1 },
      { left: 1, top: 1, width: 1, height: 1 },
      { left: 1, top: 2, width: 1, height: 1 },
    ],
  },

  // 左三右一
  theme10: {
    label: '左三右一',
    layout: { row: 3, column: 2 },
    grids: [
      { left: 0, top: 0, width: 1, height: 1 },
      { left: 0, top: 1, width: 1, height: 1 },
      { left: 0, top: 2, width: 1, height: 1 },
      { left: 1, top: 0, width: 1, height: 3 },
    ],
  },
  theme11: {
    label: '上二下三',
    layout: { row: 2, column: 3 },
    grids: [
      { left: 0, top: 0, width: 1.5, height: 1 },
      { left: 1.5, top: 0, width: 1.5, height: 1 },
      { left: 0, top: 1, width: 1, height: 1 },
      { left: 1, top: 1, width: 1, height: 1 },
      { left: 2, top: 1, width: 1, height: 1 },
    ],
  },
  theme12: {
    label: '左三中一右三',
    layout: { row: 3, column: 3 },
    grids: [
      { left: 0, top: 0, width: 1, height: 1 },
      { left: 0, top: 1, width: 1, height: 1 },
      { left: 0, top: 2, width: 1, height: 1 },
      { left: 1, top: 0, width: 1, height: 3 },
      { left: 2, top: 0, width: 1, height: 1 },
      { left: 2, top: 1, width: 1, height: 1 },
      { left: 2, top: 2, width: 1, height: 1 },
    ],
  },

  // 待扩展。。。
};
export {
  viewerLayout
}

2、根据定义的数据渲染视图
我们根据定义的 grids 中视图的组数来渲染出视图的个数。完整代码如下:

import React, { useMemo, useState } from 'react';
// 引入定义的视图数据
import { viewerLayout } from '../../../contants/index';
// 引入抽出的视图组件
import Multiview from './multiview';
import './multiview.less';

export default function multiview() {
  const [currentLay, setCurrentLay] = useState('theme1');

  const handleClcik = (key) => {
    setCurrentLay(key);
  };

  // 获取当前视图的布局数据
  const { layout, grids = [] } = useMemo(() => {
    return viewerLayout[currentLay];
  }, [currentLay]);

  return (
    <div className="view-wrap">
      <div className="view-button-wrap">
        {Object.entries(viewerLayout).map(([key, value]) => (
          <button
            style={{ marginLeft: '10px' }}
            onClick={() => handleClcik(key)}
            key={key}
          >
            {value.label}
          </button>
        ))}
      </div>

      {/* 根据数据渲染所有视图 */}
      <div className="view-content-wrap">
        {grids.map((gridItem, index) => {
          return (
            <Multiview key={index} grid={gridItem} layout={layout}>
              {index}
            </Multiview>
          );
        })}
      </div>
    </div>
  );
}

3、根据传入的视图数据计算最终样式并抽取成组件

将定义好的视图数据传入渲染的组件中,并在组件中计算出当前视图组件的位置和宽高。代码如下:

import React, { useMemo } from 'react';

export default function multiview(props) {
  const { children, grid, layout } = props;

  const handleStyleData = (divisor, dividend) => (divisor / dividend) * 100 + '%';

  // 计算当前视图位置和大小
  const style = useMemo(() => {
    const { column, row } = layout;
    const { height, left, top, width } = grid;

    return {
      top: handleStyleData(top, row),
      left: handleStyleData(left, column),
      width: handleStyleData(width, column),
      height: handleStyleData(height, row),
    };
  }, [grid, layout]);

  return (
    <div className="multiview-wrap" style={{ ...style }}>
      {/* 渲染索要展示的内容 */}
      {children}
    </div>
  );
}

组件所需样式代码(使用less语法):

.view-wrap {
  width: 100%;
  height: calc(100vh - 50px);
  // .button-wrap
  .view-content-wrap {
    border: 1px solid red;
    width: 100%;
    height: 100%;
    position: relative;
  }
}

.multiview-wrap {
  border: 1px solid black;
  position: absolute;
}
.multiview-wrap_border {
  border-color: blue;
}

四、结语

上述代码通过提前配置好视图样式数据,然后根据配置好的数据去计算出每个视图在页面中的位置,简单实现了多种布局场景,我们还可以根据自己项目的需求去扩展功能如:点击当前视图高亮,双击放大等等,还可以继续增加配置文件来实现更多的布局场景。
最后看下线上demo:demo传送门