IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    实现一个响应式的瀑布流组件

    蝼蚁之行发表于 2023-09-24 08:34:20
    love 0

    在图片网站中,通常是使用瀑布流的方式来显示图片,等宽度不等高,这样显得错落有致

    今天 使用Vue3实现一个瀑布流组件,并发布为一个npm包

    1. grid布局
    2. 支持响应式

    参考自react-plock

    1、window.matchMedia

    window.matchMedia(mediaQueryString)方法返回一个MediaQueryList对象,表示指定css媒体查询字符串的结果

    其中参数mediaQueryString表示CSS @media规则的任何媒体查询选项,比如:

    • min-width、min-height、orientation等
    const media = window.matchMedia("(max-width: 850px)")

    在MediaQueryList对象中有两个属性:

    1. matches:返回查询结果,是一个布尔值
    2. media:媒体查询的字符串

    所以,根据matches我们就可以知道是否命中当前的媒体查询选项

    并且,我们还可以通过addEventListener监听mediaQuery查询结果的变化

    media.removeEventListener('change', () => {
      console.log(media.matches)
    });

    2、瀑布流组件

    响应式控制

    根据屏幕大小确定图片的列数

    export interface PropConfigType {
      gap: number[];  // 行列的间距
      columns: number[];  // 不同媒体查询选项对应的列数
      medias?: number[];  // 媒体查询选项
    }
    import { reactive, onUnmounted } from 'vue';
    
    export function useMediaValues(config: PropConfigType) {
      const info = reactive({
        columns: 1,
        gap: 1
      });
    
      const { columns, gap, media } = config;
      if (!media) {
        return setInfo(0);
      }
    
      // 循环查询medias
      const mediaQueries = media.map(media =>
        window.matchMedia(`(min-width: ${media}px)`)
      );
    
      const onSizeChange = () => {
    
        // 记录匹配到的哪一个media
        let matches = 0;
        mediaQueries.forEach(mediaQuery => {
          mediaQuery.matches && matches++;
        });
    
        // 优先匹配后面的
        const idx = Math.min(mediaQueries.length - 1, matches);
        setInfo(idx);
      };
    
      onSizeChange();
    
      // 监听查询结果
      for (let mediaQuery of mediaQueries) {
        mediaQuery.addEventListener('change', onSizeChange);
      }
    
      onUnmounted(() => {
        for (let mediaQuery of mediaQueries) {
          mediaQuery.removeEventListener('change', onSizeChange);
        }
      });
    
      function setInfo(idx: number) {
        info.columns = columns[idx];
        info.gap = gap[idx];
        return info;
      }
    
      return info;
    }

    数据结构

    info.columns计算出瀑布流的数据结构,每列为一个数组,并存储到dataColumns

    通过for循环将fall-row给渲染出来

    当屏幕宽度变化时,会重新计算info.columns,得到一个新的dataColumms,渲染等到新的瀑布流界面

    // fall-row组件
    
    <div
      :style="{
        display: 'grid',
        rowGap: prop.gap,
        gridTemplateColumns: `minmax(0, 1fr)`
      }"
    >
      // 用户的内容通过插槽的形式添加
      <slot></slot>
    </div>
    // water-fall 组件
    <div
      :style="{
        display: 'grid',
        alignItems: 'start',
        gridColumnGap: info.gap + 'px',
        gridTemplateColumns: `repeat(${info.columns}, minmax(0, 1fr))`
      }"
    >
      <fallRow
        v-for="(columns, index) in dataColumns"
        :key="index"
        :gap="info.gap + 'px'"
      >
        // 此处为作用域插槽
        <slot
          v-for="(src, index) in columns"
          :key="index"
          :src="src"
        ></slot>
      </fallRow>
    </div>
    • 通过作用域插槽将图片的src传递给父组件

    3、使用

    // 导入组件
    import { waterFall } from ...
    
    <waterFall
      :data="datasource"
      :config="{
        columns: [1, 2, 3],
        gap: [24, 12, 6],
        medias: [640, 1024, 1280]
      }"
      v-slot="slotProps"
    >
      <img
        :src="slotProps.src"
        :style="{ width: '100%', height: 'auto' }"
      />
    </waterFall>
    • v-slot="slotProps"访问父组件的作用域,获取到src进行显示

    需要注意的是:columns、gap、medias的个数要一致

    发布npm包

    做好工程化后,就可以通过发布为npm包

    地址:vue3-plock

    现在可以通过依赖命令来安装了

    pnpm add vue3-plock

    点击codesandbox查看线上的运行

    源码已经上传github,欢迎start 😀😀


    参考:

    1. https://github.com/askides/react-plock
    2. https://developer.mozilla.org/zh-CN/docs/Web/API/Window/match...


沪ICP备19023445号-2号
友情链接