政策日历分享
介绍
政策申报日历;列举一些可申报的项目,以日历形式清晰展现各个项目开始结束节点(预申报、申报开始、申报结束、明年申报)
效果展示
日历体验视频
目录结构
policy-calendar // 政策日历模块
├─components
│ ├─DatePicker.vue // 日期选择器
│ ├─Joyride.vue // 新人引导
├─otherPage //下钻页
├─AnchorContro.ts // 锚点控制类
├─calendarDetail.vue // 页面入口
├─CustomTranslate.ts // 滚动动画类
├─DataConversion.ts // 后端数据处理转换类
├─dataProvider.ts // 数据提供层
├─tableFixHandle.ts // 表格交互注册处理
问题&难点
如何实现滚动动画
element UI 实现思想
固定头固定列表格
如何固定表头和首列
由三个独立的列表(表头、固定列、内容表格)拼装
三个列表都注册滚动回调。
当内容区域任意维度拖动时,监听到滚动回调,利用scrollTo(x,y)带动固定头和固定列滚动
同样,固定头和固定列触摸滚动时也会带动内容区域滚动。
ok,此时以为已经搞定了,然鹅…!
交互提出的问题
无法实现吸附
ux规定单元格边界要与屏幕左边界对齐,
表格单元格在边界不应显示一半。
快速滚动导致视觉反转,长块元素闪烁
比较像下面这个问题
630实现方案(最大滚动时长)
版本临近,我们实现方案是 当滚动时长超过设定的最大时间,就主动停止滚动动画(overflow-x:hidden)
这样做的好处:
滚动速度被限制了,因为在滚动加速的过程中我们就停止了滚动动画,就不会出现过快导致的视觉倒转,还有长块元素的渲染速度也不用那么大压力。
ios11以下滚动兼容(smoothscroll-polyfill)
由于局部滚动调用scrollTo直接设置值没有滚动动画,是直接跳转到指定位置的
window.scrollTo( 0, 1000 );
// 设置滚动行为改为平滑的滚动
window.scrollTo({
top: 1000,
behavior: "smooth"
});
这个类似插帧效果,但是在ios低版本有些兼容问题,可以装smoothscroll-polyfill这个垫片
列表滚动后锚点点击事件失灵
ios滚动导致点击区域错位问题 事件委托解决(Event.target)
手写滚动动画 实现一个丝滑滚动效果
封装块滚动逻辑
1、注册touchs事件;下图函数判定速率 输出滚动距离及滚动时长
V = D / T
设定四档速率,事先配置好各个速率对应的滚动距离及滚动时长
2、滚动距离和滚动时长有了,接下来就是计算每一帧的滚动距离,利用window.requestAnimationFrame 持续计算并渲染
大致步骤
- 计算帧间隔时间 记为 ms
- 计算需要多少时间片 记为 n
- 总距离 / 时间片数 = 单片距离
- 不断迭代,直到滚动到目标位置
单轴滚动
滚动动画过程中 ,另一轴不得滚动
通过 touch事件 Math.abs(move.x - start.x) > 10 || Math.abs(move.y - start.y) > 10判断是哪个轴滚动。
同时定义变量“scrollDirection” 用来当抢占锁,率先触发条件的轴利用变量锁起来,在本次滑动后续手指即使偏向另一方向也不会触发
function containerTouchMove(event: any) {
if (mianObj.popVis?.value) return false
let x = event.changedTouches[0]?.clientX - touchStartX
let y = event.changedTouches[0]?.clientY - touchStartY
if (Math.abs(x) > 10 && scrollDirection !== 'y') {
scrollDirection = 'x'
x = x * -1 + lastScrollLeft
headerDom.value.scrollTo(x, 0)
bodyDom.value.scrollTo(x, startScrollTop)
} else if (Math.abs(y) > 10 && scrollDirection !== 'x') {
scrollDirection = 'y'
y = y * -1 + startScrollTop
bodyDom.value.scrollTo(lastScrollLeft, y)
rebound(y)
leftDom.value.scrollTo(0, y)
}
}
虚拟列表
数据驱动的虚拟列表
创建能容纳所有块宽度的容器
//列表总宽度
const listWidth = computed(() => {
let length = calendarType.value == 1 ? monthDate.length : dayDate.length
return length * itemSize + 20
})
计算屏幕能放几个item
//可显示的列表项数
const visibleCount = computed(() => {
return Math.ceil(vObj.screenWidth / vObj.itemSizePx)
})
计算实际显示的数据数组
//此时的开始索引
vObj.start = Math.max(Math.floor(scrollLeft / vObj.itemSizePx), 0)
//此时的结束索引
vObj.end = vObj.start + visibleCount.value
//获取真实显示列表数据
const monthVisibleData = computed(() => {
return monthDate.slice(vObj.start, Math.min(vObj.end, monthDate.length))
})
实际 vueTools截图
无侵入实现新人引导
CSS 属性 mix-blend-mode
mix-blend-mode CSS 属性描述了元素的内容应该与元素的直系父元素的内容和元素的背景如何混合。
一个栗子
<style>
.overlay {
width: 500px;
height: 666px;
background-color: rgba(0, 0, 0, 0.5);
mix-blend-mode: hard-light; // *****关键代码 设置混合类型
}
.overlay .spotlight {
width: 200px;
height: 200px;
position: absolute;
top: 10%;
left: 40%;
background-color: gray; // *****关键代码 必须是灰色 混合出来才是透明
}
</style>
<body>
<div id="main">
// this's mian code
<img
src="https://img1.baidu.com/it/u=2285203572,3700506614&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1681232400&t=ad50fc8a2b0846cc309e8cc9dc3d42c2"
class="main"
/>
</div>
<div class="overlay">
<div class="spotlight"></div>
</div>
</body>
将其封成组件,需要新人引导时调用就ok了,隔绝了业务代码
日历的行内块结构
position: sticky;
每个状态条都是独立的行内块(inline-block),放入同一行,状态标题及副标题正好可以利用sticky(粘性布局)时期固定在屏幕内