前言
前文——项目搭建
书接上回项目搭建了基础功能。开始分析需求开发组件。本项目是模仿开发阅读的H5网站,参考的页面和业务逻辑来源于七猫小说App,掌阅小说App。
分析App首页
两个App的首页都是有一个左右滑动页面轮播页面的功能。我们怎么实现这个功能呢?
设想:
- 使用scroll-X滚动实现
- 使用transtrom
- postion属性之后left/right 根据上面的设想去实践完成,组件页面布局
开始设计组件
1.红色区域为视图显示区域
2.橙色区域为内容区域
3.其他颜色为不同页面内容显示区域
创建组件
红色为全屏视图组件
蓝色为单独页面
在Vue3中无法通过parent
属性获取父组件了,学习饿了么Plus后发现可以使用provide,inject`,通过相互约定放入(可能有更好的方案,希望大佬们勿喷)
// 父组件 swipe-view
provide("setSwiper", children.value);
// 子组件
const setSwipe: any = inject("setSwiper");
const _this = getCurrentInstance();
setSwipe.push(_this);
开始写功能
组件内部使用transtrom作为滑动主要功能 获取需要滚动的dom元素
// 获取dom 绑定元素
const titleListDom: Ref<HTMLDataElement[]> = ref([]);
由于需要动态改Style,
// tab内容滚动的距离
const transFormX = computed(() => {
const {swiperSet} = toRefs(swipeProxy)
console.log('swiperSet', swiperSet.value.positionX)
return {
transform: `translateX(${swiperSet.value.positionX}%) translateY(0)`
}
});
// tabTitle滑块样式
const sliderStyle = computed(() => {
const {direction, sliderSet} = toRefs(swipeProxy);
const {positionX, startX} = toRefs(sliderSet.value)
console.log(positionX, startX)
return {
width: startX.value == 0 ? undefined : `${startX.value}px`,
left: `${direction.value === "left" ? positionX.value + 'px' : 'unset'}`,
right: `${direction.value === "right" ? positionX.value + 'px' : 'unset'}`,
};
});
记录滑动组件内部数据
// 设置的元素值
const swipeProxy = reactive({
swiperIndex: 0, // 标识获取当前的位置
swiperSet: { // view 滑动设置
startX: 0,
startY: 0,
positionX: 0,
},
sliderSet: { // title 滑动记录
startX: 0,
positionX: 0,
defaultWidth: 0
},
startTime: new Date(),
direction: "left", //滑动方向
distanceRatio: 0, // 滑动占比
});
绑定事件
onMounted(() => {
let touchFlag = false; // 标识是否是长按
// 设置初始化参数
const touchDom = touchMain.value;
const touchWidth = touchDom.offsetWidth;
const sliderWidth = getBoundingClientRect(sliderMain.value).width;
const startTitleDom = titleListDom.value[swipeProxy.swiperIndex]
swipeProxy.sliderSet.startX = sliderWidth
swipeProxy.sliderSet.defaultWidth = sliderWidth
swipeProxy.sliderSet.positionX = getSliderPosition(startTitleDom).left;
function touchStart(event: TouchEvent) {
touchFlag = true; //设置长按不动
const touch = event.touches[0]; //获取第一个触点
const startX = Number(touch.pageX); //页面触点X坐标
const startY = Number(touch.pageY); //页面触点Y坐标
const startTime = new Date();
swipeProxy.swiperSet = Object.assign(swipeProxy.swiperSet, {
startX,
startY,
});
swipeProxy.distanceRatio = 0;
swipeProxy.startTime = startTime;
}
function touchMove(event: TouchEvent) {
if (!touchFlag) return;
const touch = event.touches[0]; //获取第一个触点
const moveX = Number(touch.pageX); //页面触点X坐标
const distance = moveX - swipeProxy.swiperSet.startX;
const direction = distance > 0 ? "right" : "left";
const lastIndex = childrenLength.value - 1
// 在第一屏幕左滑动无效
if (!swipeProxy.swiperIndex && direction === "right") return;
// 在最后一屏幕右滑动无效
if (swipeProxy.swiperIndex === lastIndex && direction === "left") return;
//记录滑动方向
swipeProxy.direction = direction;
requestAnimationFrame(() => {
// 换算出占比
const Ratio = distance / touchWidth;
// 计算出滑动距离
let transformX = (swipeProxy.swiperIndex + Ratio) * 100;
const lastPositionX = -lastIndex * 100
if (transformX >= 0) transformX = 0;
else if (transformX <= lastPositionX) transformX = lastPositionX;
swipeProxy.swiperSet.positionX = transformX;
swipeProxy.distanceRatio = Math.abs(Ratio);
// 设置顶部title滚动位置
const titleIndex = Math.abs(swipeProxy.swiperIndex);
// 获取到要滚动到下一个的位置
const nextIndex = direction === "right" ? titleIndex - 1 : titleIndex + 1;
// 计算title之间的距离
const currentTitleDom = getSliderPosition(titleListDom.value[titleIndex]);
const nexTitleDom = getSliderPosition(titleListDom.value[nextIndex]);
// (currentTitleDom.width + nexTitleDom.width) / 2 两个中心点的距离
// sliderWidth title 滑块的宽度
// 参考margin做垂直水平居中
const allNum = (currentTitleDom.width + nexTitleDom.width) / 2 + sliderWidth
const sliderDistance = swipeProxy.distanceRatio * allNum;
// 获取元素的宽度
const startX = sliderWidth + sliderDistance;
swipeProxy.sliderSet.startX = startX > allNum ? allNum : startX
const position = getSliderPosition(titleListDom.value[titleIndex]);
swipeProxy.sliderSet.positionX = position[direction]
});
}
function touchEnd() {
if (swipeProxy.distanceRatio >= 0.3) {
swipeProxy.direction === "right" ? swipeProxy.swiperIndex++ : swipeProxy.swiperIndex--;
swipeProxy.direction = swipeProxy.direction === "right" ? 'left' : 'right'
}
requestAnimationFrame(() => {
console.log(swipeProxy.swiperIndex * 100)
swipeProxy.swiperSet.positionX = swipeProxy.swiperIndex * 100;
const position:any = getSliderPosition(titleListDom.value[Math.abs(swipeProxy.swiperIndex)]);
swipeProxy.sliderSet.positionX = position[swipeProxy.direction]
requestAnimationFrame(() => {
swipeProxy.sliderSet.startX = sliderWidth;
})
touchFlag = false;
});
}
touchDom.addEventListener("touchstart", touchStart);
touchDom.addEventListener("touchmove", touchMove);
touchDom.addEventListener("touchend", touchEnd);
});
左右滑动组件基本样子已经完成,后续写的样式我也会提交到这个仓库里
gitee.com/tongbinzuo/…
总结
- 这个确实让我学习了不少Vue3的知识
- 对transform 也有了新的认识
- 代码中肯定有不合理的地方,大神勿喷。
- 后面我会继续更新的,有什么问题大家多多评论