虚拟列表【vue】等高虚拟列表/非等高虚拟列表

文章目录

  • 1、等高虚拟列表
  • 2、非等高虚拟列表

1、等高虚拟列表

参考文章1
参考文章2
在这里插入图片描述
在这里插入图片描述

<!-- eslint-disable vue/multi-word-component-names -->
<template>
  <div
    class="waterfall-wrapper"
    ref="waterfallWrapperRef"
    @scroll="handleScroll"
  >
    <div :style="scrollStyle">
      <div v-for="item in computedData.items" :key="item.userId">
        {{ item.username }}
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed, onMounted, ref } from "vue";
import { sameHighData } from "../data/index";

const itemHeight = 18; //每个item的高度
let itemCount = ref(500); // 设置总共长度为500
const scrollTop = ref(0); // 滚动的高度
const wrapperHeight = ref(0); // 滚动容器的高度
const waterfallWrapperRef = ref();

const list = ref([...sameHighData]);

const handleScroll = (e: any) => {
  // 在content内容中距离content盒子的上部分的滚动距离
  scrollTop.value = e.target.scrollTop;

  // 判断是否接近底部,如果是则加载更多数据
  if (e.target.scrollHeight - e.target.clientHeight - scrollTop.value < 100) {
    getData();
  }
};

const getData = () => {
  list.value = list.value.concat(list.value);
  itemCount.value = list.value.length;
  // scrollBarRef.value.style.height = itemCount.value * itemHeight + "px";
  console.log("发送网络请求", list.value.length);
};

const scrollStyle = computed(() => ({
  height: `${
    itemHeight * (list.value.length - computedData.value.startIndex)
  }px`,
  transform: `translateY(${itemHeight * computedData.value.startIndex}px)`,
}));

// 这里不使用方法的原因是以来了scrollTop 响应式数据来变更 所以使用computed更方便
const computedData = computed(() => {
  // 可视区域的索引
  const startIndex = Math.floor(scrollTop.value / itemHeight);

  // 设置上缓冲区为2 上缓冲区的索引
  const finialStartIndex = Math.max(0, startIndex - 2);

  // 可视区显示的数量 这里设置了item的高度为50
  const numVisible = Math.floor(wrapperHeight.value / itemHeight);

  // 设置下缓冲区的结束索引 上缓冲区也设置为2
  const endIndex = Math.min(itemCount.value, startIndex + numVisible + 2);

  // 将上缓冲区到下缓冲区的数据返回
  const items: any = [];

  // contentWrapRef.value!.style.height = finialStartIndex * itemHeight + "px";
  for (let i = finialStartIndex; i < endIndex; i++) {
    const item = {
      ...list.value[i],
      top: itemHeight * i + "px",
      transform: `translateY(${itemHeight * i + "px"})`,
    };
    items.push(item);
  }
  console.log(startIndex);

  return { items, startIndex };
});

onMounted(() => {
  // 在页面一挂载就获取滚动区域的高度
  if (waterfallWrapperRef.value) {
    wrapperHeight.value = waterfallWrapperRef.value?.clientHeight;
  }
});
</script>

<style scoped>
.waterfall-wrapper {
  height: 500px;
  overflow: hidden;
  overflow-y: scroll;
  position: relative;
  .scroll-bar {
    width: 100%;
    position: absolute;
  }
}
</style>



2、非等高虚拟列表

参考链接

非等高序列列表的实现逻辑并没有通过相对定位的方式,而是通过设置translateY来实现滚动
思路:

  • 难点1:每个元素的高度不一样,没办法直接计算出容器的高度
    • 容器高度的作用是足够大 可以让用户进行滚动,所以我们可以直接假设每一个元素的高度 需要保证预测的 item 高度尽量比真实的每一项 item 的高度要小或者接近所有 item 高度的平均值
  • 难点2: 每个元素高度不一样,top值不能通过count * index算出来
  • 难点3: 每个元素高度不一样,不能通过scrollTop * size计算出已经滚动的元素个数,很难获取可视区的起始索引
    • 难度2和难度3的解决方案是先把数据渲染到页面上 之后再通过 获取正式dom的高度 来计算
      在这里插入图片描述
<template>
  <div
    class="waterfall-wrapper"
    ref="waterfallWrapperRef"
    @scroll="handleScroll"
  >
    <!-- 内容显示的区域 -->
    <div ref="listRef" :style="scrollStyle">
      <div v-for="(item, index) in computedData" :key="index">
        {{ index }} {{ item.sentence }}
      </div>
    </div>
  </div>
</template>
  • 1、先确定预测的item高度和数据
  • 2、获取滚动区域的高度且计算容器最大容量
onMounted(async () => {
  // 在页面一挂载就获取滚动区域的高度
  if (waterfallWrapperRef.value) {
    wrapperHeight.value = waterfallWrapperRef.value?.clientHeight;
    // 预测容器最多显示多少个元素
    maxCount.value = Math.ceil(wrapperHeight.value / estimatedHeight) + 1;
    await nextTick();

    // 先把数据显示再页面上
    initData();
    setItemHeight();
  }
});
  • 3、引用一个新的变量来存放实际的dom和预测结果和实际的偏差高度
interface IPosInfo {
  // 当前pos对应的元素索引
  index: number;
  // 元素顶部所处位置
  top: number;
  // 元素底部所处位置
  bottom: number;
  // 元素高度
  height: number;
  // 自身对比高度差:判断是否需要更新
  dHeight: number;
}

在这里插入图片描述

  • 4、初始化预测的dom

参考逻辑

function initPosition() {
  const pos: IPosInfo[] = [];
	//  将获取到的数据全部进行初始化
  for(let i = 0; i < props.dataSource.length; i++) {
    pos.push({
      index: item.id,
      height: props.estimatedHeight, // 使用预测高度先填充 positions
      top: item.id * props.estimatedHeight,
      bottom: (item.id + 1) * props.estimatedHeight,
      dHeight: 0,
    })
  }
  positions.value = pos;
}

实际代码

// 初始化数据
const initData = () => {
  const items: IPosInfo[] = [];
  // 获取需要更新位置的dom长度 即新增加的数据
  const len = list.value.length - preLen.value;
  // 已经处理好位置的长度
  const currentLen = initList.value.length;

  // 可以用三元运算符是因为初始化的时候initList的数值是空的
  const preTop = initList.value[currentLen - 1]
    ? initList.value[currentLen - 1].top
    : 0;
  const preBottom = initList.value[currentLen - 1]
    ? initList.value[currentLen - 1].bottom
    : 0;

  for (let i = 0; i < len; i++) {
    const currentIndex = preLen.value + i;
    // 获取当前要初始化的item
    const item: DiffHigh = list.value[currentIndex];
    items.push({
      id: item.id,
      height: estimatedHeight, // 刚开始不知道他高度 所以暂时先设置为预置的高度
      top: preTop
        ? preTop + i * estimatedHeight
        : currentIndex * estimatedHeight,
      // 元素底部所处位置 这里获取的是底部的位置 所以需要+1
      bottom: preBottom
        ? preBottom + (i + 1) * estimatedHeight
        : (currentIndex + 1) * estimatedHeight,
      // 高度差:判断是否需要更新
      dHeight: 0,
    });
  }
  initList.value = [...initList.value, ...items];
  preLen.value = list.value.length;
};
  • 5、更新实际的数据,拿到实际的数据高度
    • 通过 ref 获取到 list DOM,进而获取到它的 children
    • 遍历 children,针对于每一个 DOM item 获取其高度信息,通过其 id 属性找到 positions 中对应的 item,更新该 item 信息
// 数据渲染之后更新item的真实高度
const setItemHeight = () => {
  const nodes = listRef.value.children;

  if (!nodes.length) return;

  //  Array.from(nodes): 使用 Array.from() 方法,其中 nodes 是一个类数组对象。
  // [...nodes]: 使用数组解构语法,其中 nodes 是一个可迭代对象,例如 NodeList。
  // 1、遍历节点并获取位置信息
  // 2、更新节点高度和位置信息:
  // 这里只更新了视图上的bottom 没有更新top的数值 而且只更新视图上面显示的,并没有更新整个列表,因为height发生了改变视图以外的数据也发生了变化 需要同步修改
  [...nodes].forEach((node: Element, index: number) => {
    const rect = node.getBoundingClientRect();
    const item = initList.value[startIndex.value + index];
    const dHeight = item?.height - rect?.height;
    if (dHeight) {
      item.height = rect.height;
      item.bottom = item.bottom - dHeight;
      item.dHeight = dHeight;
    }
  });
  // 3、将当前 item 的 dHeight 进行累计,之后再重置为 0 (更新后就不再存在高度差了)
  const len = initList.value.length;
  let startHeight = initList.value[startIndex.value]?.dHeight;
  if (startHeight) {
    initList.value[startIndex.value].dHeight = 0;
  }
	// 从渲染视图的第二个开始处理
	// 实际上第一项的 top 值为 0,bottom 值在上轮也更新过了,所以遍历的时候我们从第二项开始
  for (let i = startIndex.value + 1; i < len; i++) {
    const item = initList.value[i];
    item.top = initList.value[i - 1].bottom;
    item.bottom = item.bottom - startHeight;
    if (item.dHeight !== 0) {
      startHeight += item.dHeight;
      item.dHeight = 0;
    }
  }
  // 设置 list 高度
  listHeight.value = initList.value[len - 1].bottom;
};
  • 6、设置滚动

// 已经滚动的距离
const offsetDis = computed(() =>
  startIndex.value > 0 ? initList.value[startIndex.value - 1]?.bottom : 0
);

const scrollStyle = computed(() => ({
  height: `${listHeight.value - offsetDis.value}px`,
  transform: `translateY(${offsetDis.value}px)`,
}));

在这里插入图片描述

  • 7、滚动事件和 startIndex 计算
    • 如何判断一个 item 滚出视图?这个问题在最早就提到过了,只需要看它的 bottom <= scrollTop
    • 现在就好办了,我们可以遍历 positions 数组找到第一个 item.bottom >= scrollTop 的 item,它就是 startIndex 所对应的 item,那 startIndex 就拿到了
    • 这里再补充一个细节,在 initList 数组中 item.bottom 一定是递增的,而我们现在想要做的是查找操作,有序递增 + 查找 = 二分查找
// 二分查找 找到可视区域的索引startIndex
const getStartIndex = (list: any, value: number) => {
  let left = 0,
    right = list.length - 1,
    templateIndex = -1;

  while (left < right) {
    const mid = Math.floor((left + right) / 2);
    const midValue = list[mid].bottom;
    // 如果找到了就用找到的索引 + 1 作为 startIndex,因为找到的 item 是它的 bottom 与 scrollTop 相等,即该 item 已经滚出去了
    if (midValue === value) return mid + 1;
    else if (midValue < value) left = mid + 1;
    else if (midValue > value) {
      if (templateIndex == -1 || templateIndex > mid) {
        templateIndex = mid;
      }
      right = mid;
    }
  }
  return templateIndex;
};
  • 8、每次 startIndex 改变,不仅会改变 renderList 的计算,我们还需要重新计算 item 信息
watch(
  () => startIndex.value,
  () => {
    setItemHeight();
  }
);
<!-- eslint-disable vue/multi-word-component-names -->
<template>
  <div
    class="waterfall-wrapper"
    ref="waterfallWrapperRef"
    @scroll="handleScroll"
  >
    <!-- 内容显示的区域 -->
    <div ref="listRef" :style="scrollStyle">
      <div v-for="(item, index) in computedData" :key="index">
        {{ index }} {{ item.sentence }}
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed, nextTick, onMounted, ref, watch } from "vue";
import { sameDiffData } from "../data/index";

interface IPosInfo {
  // 当前pos对应的元素索引
  id: number;
  // 元素顶部所处位置
  top: number;
  // 元素底部所处位置
  bottom: number;
  // 元素高度
  height: number;
  // 高度差:判断是否需要更新
  dHeight: number;
}

interface DiffHigh {
  id: number;
  sentence: string;
}

const waterfallWrapperRef = ref();
const listRef = ref();

const estimatedHeight = 60; // 设置预估的高度为100
let itemCount = ref(500); // 设置总共长度为500
const scrollTop = ref(0); // 滚动的高度
const wrapperHeight = ref(0); // 滚动容器的高度
const preLen = ref(0); // 因为每次处理数值的时候都需要处理全部数据 这个用来记录已经处理的数据

const list = ref<DiffHigh[]>([...sameDiffData]);
const initList = ref<IPosInfo[]>([]); // 用来存放已经处理好位置的数据
const listHeight = ref(0); // 列表的高度

const startIndex = ref(0);
const maxCount = ref(0);

const endIndex = computed(() =>
  Math.min(list.value.length, startIndex.value + maxCount.value + 2)
);

const computedData = computed(() =>
  list.value.slice(Math.max(0, startIndex.value - 2), endIndex.value)
);

// 已经滚动的距离
const offsetDis = computed(() =>
  startIndex.value > 0 ? initList.value[startIndex.value - 1]?.bottom : 0
);

const scrollStyle = computed(() => ({
  height: `${listHeight.value - offsetDis.value}px`,
  transform: `translateY(${offsetDis.value}px)`,
}));

const handleScroll = (e: any) => {
  scrollTop.value = e.target.scrollTop;

  // 在content内容中距离content盒子的上部分的滚动距离
  // 在开始滚动之后获取startIndex
  startIndex.value = getStartIndex(initList.value, scrollTop.value);
  console.log(startIndex.value, "-0-");
  // 判断是否接近底部,如果是则加载更多数据
  if (e.target.scrollHeight - e.target.clientHeight - scrollTop.value < 100) {
    getMoreData();
  }
};

watch(
  () => startIndex.value,
  () => {
    setItemHeight();
  }
);

const getMoreData = async () => {
  list.value = list.value.concat(list.value);
  await nextTick();
  initData();
  setItemHeight();
};

// 二分查找 找到可视区域的索引startIndex
const getStartIndex = (list: any, value: number) => {
  let left = 0,
    right = list.length - 1,
    templateIndex = -1;

  while (left < right) {
    const mid = Math.floor((left + right) / 2);
    const midValue = list[mid].bottom;
    if (midValue === value) return mid + 1;
    else if (midValue < value) left = mid + 1;
    else if (midValue > value) {
      if (templateIndex == -1 || templateIndex > mid) {
        templateIndex = mid;
      }
      right = mid;
    }
  }
  return templateIndex;
};

// 初始化数据
const initData = () => {
  const items: IPosInfo[] = [];
  // 获取需要更新位置的dom长度
  const len = list.value.length - preLen.value;
  // 已经处理好位置的长度
  const currentLen = initList.value.length;

  // 可以用三元运算符是因为初始化的时候initList的数值是空的
  const preTop = initList.value[currentLen - 1]
    ? initList.value[currentLen - 1].top
    : 0;
  const preBottom = initList.value[currentLen - 1]
    ? initList.value[currentLen - 1].bottom
    : 0;

  for (let i = 0; i < len; i++) {
    const currentIndex = preLen.value + i;
    // 获取当前要初始化的item
    const item: DiffHigh = list.value[currentIndex];
    items.push({
      id: item.id,
      height: estimatedHeight, // 刚开始不知道他高度 所以暂时先设置为预置的高度
      top: preTop
        ? preTop + i * estimatedHeight
        : currentIndex * estimatedHeight,
      // 元素底部所处位置 这里获取的是底部的位置 所以需要+1
      bottom: preBottom
        ? preBottom + (i + 1) * estimatedHeight
        : (currentIndex + 1) * estimatedHeight,
      // 高度差:判断是否需要更新
      dHeight: 0,
    });
  }
  initList.value = [...initList.value, ...items];
  preLen.value = list.value.length;
};

// 数据渲染之后更新item的真实高度
const setItemHeight = () => {
  const nodes = listRef.value.children;

  if (!nodes.length) return;

  //  Array.from(nodes): 使用 Array.from() 方法,其中 nodes 是一个类数组对象。
  // [...nodes]: 使用数组解构语法,其中 nodes 是一个可迭代对象,例如 NodeList。
  // 1、遍历节点并获取位置信息
  // 2、更新节点高度和位置信息:
  [...nodes].forEach((node: Element, index: number) => {
    const rect = node.getBoundingClientRect();
    const item = initList.value[startIndex.value + index];
    const dHeight = item?.height - rect?.height;
    if (dHeight) {
      item.height = rect.height;
      item.bottom = item.bottom - dHeight;
      item.dHeight = dHeight;
    }
  });
  // 3、将当前 item 的 dHeight 进行累计,之后再重置为 0 (更新后就不再存在高度差了)
  const len = initList.value.length;
  let startHeight = initList.value[startIndex.value]?.dHeight;
  if (startHeight) {
    initList.value[startIndex.value].dHeight = 0;
  }

  for (let i = startIndex.value + 1; i < len; i++) {
    const item = initList.value[i];
    item.top = initList.value[i - 1].bottom;
    item.bottom = item.bottom - startHeight;
    if (item.dHeight !== 0) {
      startHeight += item.dHeight;
      item.dHeight = 0;
    }
  }
  // 设置 list 高度
  listHeight.value = initList.value[len - 1].bottom;
};

onMounted(async () => {
  // 在页面一挂载就获取滚动区域的高度
  if (waterfallWrapperRef.value) {
    wrapperHeight.value = waterfallWrapperRef.value?.clientHeight;
    // 预测容器最多显示多少个元素
    maxCount.value = Math.ceil(wrapperHeight.value / estimatedHeight) + 1;
    await nextTick();

    // 先把数据显示再页面上
    initData();
    setItemHeight();
  }
});
</script>

<style scoped>
.waterfall-wrapper {
  height: 300px;
  /* overflow: hidden; */
  overflow-y: scroll;
  /* position: relative; */
  .scroll-bar {
    width: 100%;
  }
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/403432.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue里echarts的使用:画饼图和面积折线图

vue里echarts的使用,我们要先安装echarts,然后在main.js里引入: //命令安装echarts npm i echarts//main.js里引入挂载到原型上 import echarts from echarts Vue.prototype.$echarts = echarts最终我们实现的效果如下: 头部标题这里我们封装了一个全局公共组件common-he…

Spark: a little summary

转眼写spark一年半了&#xff0c;从之前写机器学习组件、做olap到后面做图计算&#xff0c;一直都是用的spark&#xff0c;惭愧的是没太看过里面的源码。这篇文章的目的是总结一下Spark里面比较重要的point&#xff0c;重点部分会稍微看一下源代码&#xff0c;因为spark是跟cli…

基于springboot+vue的靓车汽车销售网站(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

Android中Transition过渡动画的简单使用

前些天发现了一个蛮有意思的人工智能学习网站,8个字形容一下"通俗易懂&#xff0c;风趣幽默"&#xff0c;感觉非常有意思,忍不住分享一下给大家。 &#x1f449;点击跳转到教程 一、布局xml文件代码如下&#xff1a; <?xml version"1.0" encoding&quo…

【ctfshow—web】——信息搜集篇1(web1~20详解)

ctfshow—web题解 web1web2web3web4web5web6web7web8web9web10web11web12web13web14web15web16web17web18web19web20 web1 题目提示 开发注释未及时删除 那就找开发注释咯&#xff0c;可以用F12来查看&#xff0c;也可以CtrlU直接查看源代码呢 就拿到flag了 web2 题目提示 j…

H5移动端文件预览pdf

H5移动端文件预览pdf 需求&#xff1a;H5页面嵌入浙政钉&#xff0c;需要文件预览Pdf。 试用了多个插件&#xff0c;踩了很多坑&#xff0c;如果小伙伴有类似填坑经历&#xff0c;并成功解决&#xff0c;感谢留言指点&#xff01;&#xff01;&#xff01; 先讲最终方案&#x…

[云原生] 二进制安装K8S(中)

书接上文&#xff0c;我们继续部署剩余的插件 一、K8s的CNI网络插件模式 2.1 k8s的三种网络模式 K8S 中 Pod 网络通信&#xff1a; &#xff08;1&#xff09;Pod 内容器与容器之间的通信 在同一个 Pod 内的容器&#xff08;Pod 内的容器是不会跨宿主机的&#xff09;共享…

VG5032VDN 电压控制的晶体振荡器 (VCXO) 输出:LVDS

在今天繁复多变的电子市场中&#xff0c;设计师不断寻求更稳定、更灵活的时钟解决方案&#xff0c;以满足从通信网络到工业控制系统的广泛应用。VG5032VDN VCXO是一款高性能的电压控制晶体振荡器 它结合了高性能、多用途性和紧凑设计&#xff0c;是一款适合广泛应用的晶体振荡…

玩主机游戏能省去不少烦恼?+主机该购买哪台?

文/嘉兰SK 来到次世代&#xff0c;玩家们最关心的问题逐渐变成了购买的游戏能否支持升级。 各个游戏厂商也没有闲着。 此前还有标准版、黄金版、终极版、决定版等一系列。 想出很多招数。 于是很多新玩家开始疑惑&#xff1a;你们都说玩主机游戏可以省去很多麻烦&#xff0c;可…

航空航天5G智能工厂数字孪生可视化平台,推进航空航天数字化转型

航空航天5G智能工厂数字孪生可视化平台&#xff0c;推进航空航天数字化转型。随着科技的不断发展&#xff0c;数字化转型已经成为各行各业关注的焦点。航空航天业作为高端制造业的代表&#xff0c;也在积极探索数字化转型之路。为了更好地推进航空航天数字化转型&#xff0c;一…

设置主从复制时发生报错Could not find first log file name in binary log index file‘;解决方案

如图所示&#xff0c;slave_io_runnind:no,slave_sql_running:yes 此时&#xff0c;主从配置错误&#xff0c;我们可以查看Last_IO_Error:来查看报错信息 此时&#xff0c;我们需要停止从服务器的主从服务&#xff0c; mysql> stop slave; Query OK, 0 rows affected, 1 w…

【汽车电子】万字详解汽车标定与XCP协议

XCP协议基础 文章目录 XCP协议基础一、引言1.1 什么是标定1.2 什么时候进行标定1.3 标定的意义 二、XCP协议简介2.1 xcp简介2.2 XCP如何加快开发过程&#xff1f;2.3 XCP的主要作用 三、XCP工作过程3.1 工作过程3.2 通讯模型3.3 测量与标定 四、XCP报文解析4.1 数据包报文格式4…

petalinux_zynq7 驱动DAC以及ADC模块之六:qt显示adc波形

前文&#xff1a; petalinux_zynq7 C语言驱动DAC以及ADC模块之一&#xff1a;建立IPhttps://blog.csdn.net/qq_27158179/article/details/136234296petalinux_zynq7 C语言驱动DAC以及ADC模块之二&#xff1a;petalinuxhttps://blog.csdn.net/qq_27158179/article/details/1362…

【C语言】走迷宫之推箱子

前言&#xff1a; 在上一篇文章当中我介绍了一个走迷宫的写法&#xff0c;但是那个迷宫没什么可玩性和趣味性&#xff0c;所以我打算在迷宫的基础上加上一个推箱子&#xff0c;使之有更好的操作空间&#xff0c;从而增强了游戏的可玩性和趣味性。 1. 打印菜单 void menu() {…

BUUCTF第二十四、二十五题解题思路

目录 第二十四题CrackRTF 第二十五题[2019红帽杯]easyRE1 第二十四题CrackRTF 查壳 无壳&#xff0c;32位&#xff0c;用32位IDA打开&#xff0c;打开后的main函数很短&#xff0c;可以找到一句“jmz _main_0”——跳转到 _main_0&#xff0c;说明真正的主函数是_main_0&am…

opencv图像的本质

目的 OpenCV是一个跨平台的库&#xff0c;使用它我们可以开发实时的计算机视觉应用程序。 它主要集中在图像处理&#xff0c;视频采集和分析&#xff0c;包括人脸检测和物体检测等功能。 数字图像在计算机中是以矩阵形式存储的&#xff0c;矩阵中的每一个元素都描述一定的图像…

学生个性化成长平台搭建随笔记

1.Vue的自定义指令 在 Vue.js 中&#xff0c;我们可以通过 Vue.directive() 方法来定义自定义指令。具体来说&#xff0c;我们需要传递两个参数&#xff1a; 指令名称&#xff1a;表示我们要定义的指令名称&#xff0c;可以是一个字符串值&#xff0c;例如&#xff1a;has-rol…

时域相位分析技术 和空域相位分析技术

l) 时域相位分析技术 在光 学测量 的许 多情况 下 &#xff0c; 时变图像信 号 的背景光 强 与调制 度可 以看作是 常 数 &#xff0c;并且 其光 强 随时 间 的变化也满足 正 弦条件 。 那 么针 对某 一 空 间采样 点 (x &#xff0c;y) &#xff0c; 某时刻 采 集到 的光 强 可…

CleanMyMacX4.15破解版下载安装包步骤教程

安装CleanMyMac X的步骤如下&#xff1a; 在中文网站上进行安装包的免费下载。找到下载完成的安装包&#xff0c;然后双击打开。用鼠标拖动CleanMyMac X应用程序的图标&#xff0c;将其拖放至右侧的“应用程序”文件夹内。稍等片刻&#xff0c;CleanMyMac X应用程序就会出现在…

使用 package.json 配置代理解决 React 项目中的跨域请求问题

使用 package.json 配置代理解决 React 项目中的跨域请求问题 当我们在开发前端应用时&#xff0c;经常会遇到跨域请求的问题。为了解决这个问题&#xff0c;我们可以通过配置代理来实现在开发环境中向后端服务器发送请求。 在 React 项目中&#xff0c;我们可以使用 package…