请问老师关闭动画的逻辑
来源:17-15 将基础滑动列表应用到数据大屏

hy_wang
2020-10-08
老师这里的Promise之前添加这个isAnimationStart意义是什么我不太清楚。我理解正常来讲如果config更新触发watch,那么watch中的代码是这样的
watch中的代码stopAnmiation和isAnimationStart的赋值是同步逻辑,在watch触发到执行这段代码,对于上方Promise中的代码并不会执行(JS单线程性质)。所以我认为是无意义的中止动画执行,因为并不存在先执行了stopAnimation这个函数,然后正在执行update函数的逻辑中js会穿插执行Promise中的异步代码。一定是update方法执行完毕之后才会执行其他等待的异步代码。请问老师是我理解错误了吗,如果哪里有不对希望老师可以指出。我认为是config更新后触发watch然后actualConfig是ref的变量会自动更新,从而动画会自动变成更新后的值进行执行。(停止动画的逻辑永远不会被js执行到)。劳烦老师指正,会存在update函数执行到stopAnimation更新值为false而不继续执行update函数之后的逻辑先执行Promise的逻辑吗?
写回答
2回答
-
hy_wang
提问者
2020-10-09
<template> <div class="base-scroll-list" ref="container" > <div class="base-scroll-header" :style="{'background-color':actualConfig.headerBg,'height':`${+actualConfig.headerHeight}px`, fontSize:`${actualConfig.headerFontSize}px`, color:actualConfig.headerColor}" > <div class="header-item text" v-for="(headerItem,index) in headerData" :key="index" :align="actualConfig.headerAlign" :style="{width:`${headerWidth[index]}px`,...headerStyle[index]}" v-html="headerItem" /> </div> <div class="base-scroll-rows-wapper" :style="{height:`${wholeHeight - actualConfig.headerHeight}px`}" > <!-- 注意这里div的key 要求每次数据更新之后就重新渲染 --> <!-- 如果使用rowIndex的话diff算法就会复用之前div 第一个高度就会从0变成72 --> <!-- 使用不同的key直接渲染之前的div 那么就会进行transition变化 --> <!-- 而使用不同的key值时 就会直接重新渲染不同的div 所以从0-72高度的变化过程就不会进行transition --> <div class="base-scroll-rows" v-for="(rowData,rowIndex) in currentRowData" :key="rowData.index" :style="{backgroundColor:rowBgColor(rowData.index), fontSize:`${actualConfig.rowFontSize}px`, height:`${rowsHeight[rowIndex]}px`, lineHeight:`${rowsHeight[rowIndex]}px`, color:actualConfig.rowColor}" > <!-- 注意style绑定的是对象,所以这里使用了对象的扩展运算符合并 --> <div class="base-scroll-list-column" v-for="(colData,colIndex) in rowData.value" :style="{width:`${headerWidth[colIndex]}px`,...rowStyle[colIndex]}" :key="colIndex" :align="actualConfig.rowsAlign" > {{colData}} </div> </div> </div> </div> </template> <script> import { ref, onMounted, nextTick, watch } from "vue"; import cloneDeep from "lodash/cloneDeep"; import assign from "lodash/assign"; import { useScreen } from "../../hooks/useScreen"; const mockData = [ ["张三", "22", 10000], ["李四", "24", 15000], ["王浩羽", "26", 24000], ["王浩羽1", "26", 24000], ["王浩羽2", "26", 24000], ["王浩羽3", "26", 24000], ["王浩羽4", "26", 24000], ["ddd", "26", 24000], ["xxx", "26", 24000], ["aaa", "26", 24000], ]; const defaultConfig = { headerData: ["姓名", "年龄", "月薪"], headerStyle: [{ color: "red", width: "200px" }], headerBg: "rgb(90,90,90)", headerHeight: 48, headerIndex: true, headerIndexContext: "#", headerAlign: "center", headerFontSize: 32, headerColor: "#fff", headerIndexContextStyle: { color: "green", width: "20px", }, data: mockData, rowNumber: 10, // 每页显示行数 // 序号列内容的样式 rowIndexStyle: { color: "red", }, // 表体样式 rowStyle: [{ color: "green" }], // 奇偶行背景颜色 rowBg: ["rgb(40,40,40)", "rgb(55,55,55)"], rowFontSize: 28, rowColor: "#000", rowsAlign: "center", moveItems: 1, duration: 2, }; export default { name: "BaseScrollList", props: { config: { type: Object, default: () => {}, }, }, setup(props, ctx) { const container = ref(null); const actualConfig = ref({}); const headerData = ref([]); const headerStyle = ref([]); const headerWidth = ref([]); const rowStyle = ref([]); const rowsData = ref([]); // 总的 const currentRowData = ref([]); // 当前展示 const currentIndex = ref(0); const rowItemHeight = ref(0); // 每行行高 const rowsHeight = ref([]); // 数组 行高 const wholeHeight = ref(0); let stopAnimation = false; // 处理props Header逻辑 const handleConfigHeader = (config) => { const _headerData = cloneDeep(config.headerData); const _headerStyle = cloneDeep(config.headerStyle); const _rowStyle = cloneDeep(config.rowStyle); if (!_headerData?.length) return; if (config.headerIndex) { _headerData.unshift(config.headerIndexContext); _headerStyle.unshift(config.headerIndexContextStyle); _rowStyle.unshift(config.rowIndexStyle); } headerData.value = _headerData; headerStyle.value = _headerStyle; rowStyle.value = _rowStyle; const { width, height } = useScreen(container.value); computedHeaderArea(width, height); }; // 处理props Data逻辑 const handleConfigData = (config) => { rowsData.value = config.data || []; if (config.headerIndex) { // 处理序号列 rowsData.value.forEach((item, index) => { item.unshift(index + 1); }); } // 计算高度 computedHeight(config); }; // 奇偶列背景颜色 const rowBgColor = (index) => { const bgColor = index % 2 === 0 ? actualConfig.value.rowBg[0] : actualConfig.value.rowBg[1]; return bgColor; }; // 计算表体高度 const computedHeight = (config) => { const { width, height } = useScreen(container.value); const { headerHeight, rowNumber } = config; const unUsedHeight = height.value - headerHeight; // rowHeight.value = Math.floor(unUsedHeight / rowNumber); // 可删除变量 rowHeight const rowHeight = Math.floor(unUsedHeight / rowNumber); rowItemHeight.value = rowHeight; rowsHeight.value = new Array(rowsData.value.length).fill(rowHeight); }; // 计算表头宽度/高度 const computedHeaderArea = (width, height) => { let useWidth = 0; let useCount = 0; const length = headerData.value.length; const _headerStyle = headerStyle.value; // 查找是否存在自定义width _headerStyle.forEach((style) => { // 存在 按照自定义width判断 if (style.width) { // 计算自定义宽度总数和总个数 useWidth = +style.width.replace("px", ""); useCount++; } }); // 动态计算列宽时 使用剩余的宽度除以剩余列数 const _widthCount = Math.floor( (width.value - useWidth) / (length - useCount) ); const arrayWidth = new Array(length).fill(_widthCount); // 计算结果后再次循环 替换arrayWidth _headerStyle.forEach((style, index) => { // 存在 按照自定义width判断 if (style.width) { // 替换对应宽度 arrayWidth[index] = +style.width.replace("px", ""); } }); headerWidth.value = arrayWidth; }; // 初始化整体数据 需要做额外兼容处理 const initRowData = (rowNumber, data) => { const length = data.length; if (length > rowNumber) { return data.map((i, idx) => { return { value: i, index: idx, }; }); } if (length * 2 > rowNumber) { const newData = [...cloneDeep(data), ...cloneDeep(data)]; return newData.map((i, idx) => { return { value: i, index: idx, }; }); } }; // 经典基础算法 滚动算法 const startAnmiation = async () => { if (stopAnimation) { nextTick(() => { update(); }); return; } const { rowNumber, moveItems, duration } = actualConfig.value; const _rowData = initRowData(rowNumber, rowsData.value); const length = _rowData.length; if (length < rowNumber) { currentRowData.value = _rowData; } else { // 开启动画 const data = _rowData.slice(currentIndex.value); data.push(..._rowData.slice(0, currentIndex.value)); currentRowData.value = data; // 第一次渲染出来之后 我先让先头的个数 高度为0 消失 然后在进行重新渲染(这个时候用户无法察觉变化) // 视觉上就是动画元素往上移动了 rowsHeight.value = new Array(length).fill(rowItemHeight.value); // 先重新渲染 0.3s 后 进行动画展示 // 这个Promise 更新页面 await new Promise((res) => setTimeout(res, 300)); if (stopAnimation) { nextTick(() => { update(); }); return; } // 使用nextTick的话 第一次页面渲染之后 直接就将height变为了0 // await nextTick(); // 为什么这里不可以? // 页面已经更新了 开始进行动画 rowsHeight.value.splice(0, moveItems, ...new Array(moveItems).fill(0)); // 动画 0.3s后结束 等待1s后 更改currentIndex的值 其实也就是延迟渲染 不然就会立马渲染 await new Promise((res) => setTimeout(res, duration * 1000)); if (stopAnimation) { nextTick(() => { update(); }); return; } currentIndex.value += moveItems; const lasted = currentIndex.value - length; if (lasted >= 0) { currentIndex.value = lasted; } await startAnmiation(); } }; const update = async () => { // 关闭动画 const _config = assign(defaultConfig, props.config); // 拿到总高度 const { height } = useScreen(container.value); wholeHeight.value = height.value; handleConfigHeader(_config); handleConfigData(_config); actualConfig.value = _config; // 开启动画 stopAnimation = false; startAnmiation(); }; watch( () => props.config, () => { stopAnimation = true; }, { deep: true, } ); onMounted(() => { update(); }); return { container, headerData, headerStyle, actualConfig, headerWidth, rowsData, currentRowData, rowsHeight, rowStyle, rowBgColor, wholeHeight, }; }, }; </script> <style lang="scss" scoped> .base-scroll-list { height: 100%; width: 100%; } .base-scroll-header { display: flex; align-items: center; .text { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .header-item { padding: 0 10px; box-sizing: border-box; } } .base-scroll-rows-wapper { overflow: hidden; .base-scroll-rows { overflow: hidden; transition: all 0.3s linear; display: flex; .base-scroll-list-column { padding: 0 10px; box-sizing: border-box; display: flex; flex-direction: column; justify-content: center; } } } </style>
00 -
扬_灵
2020-10-09
同学你好,我今天测试了一下我们课程中关于关闭动画的逻辑,是有些问题的,在这里你可以通过监听isAnimationStart的值来控制动画的开启和关闭,你可以看一下这是我今天测试的使用的代码是使用按钮来控制的
const handleChange = () => { if (isAnimationStart.value) { isAnimationStart.value = false stopAnimation() } else { isAnimationStart.value = true startAnimation() } }
032020-10-10
相似问题