前言
和我上次的不使用插件与组件库 - 如何简单实现下拉刷新与上滑加载,算是一个系列吧,算是自己思考,然后实现的一种留痕方式。
很多人都在说不要重复造轮子,咱也不造轮子,最起码要有可以造轮子的简单思想,不论是写什么都有一定的用处。
这个东西实现起来没有什么难度,主要是有一些小的弯弯在里面,下面我会细说一下,具体问题。
准备
老样子,听个大概
实现效果
- 可以左滑动 ?
- 按钮怎么去写 ?
- 可以有层次的出现 ?
- 有点点的弹性效果 ?
实现思路
来说一下,实现上述的效果有什么方法,我先抛一下愚见。 先给出HTML的结构
<div class="swipe-cell">
<div class="swipe">
<div class="container">
内容区域
</div>
<div class="btn-right">
存放按钮区域
</div>
</div>
</div>
可以左滑动
- 左滑动:使用
transform: translateX()
属性。 - 滑多少:使用js获取DOM,去拿到每一个按钮的宽度
clientWidth
,滑动的是总宽度。
按钮怎么去写
- 按钮的设计:整体存放在一个
btn-right
的类名盒子里,该盒子是定位在内容的右侧的,因为是流布局的页面模式,div
又是块级元素,采用绝对定位,按钮很容易出现在了,可视窗口的右侧了。 - 按钮的初始化:不论几个按钮都采用
left:0
放到container
盒子的右侧,堆叠在一块,为下面的有层次的出现,做伏笔。
可以有层次的出现
- 层次的出现:层次的出现不难,前面有了滑动的总距离,每一个按钮的大小,又在同一起点,所以采用一定的比例去移动,跟随上一家按钮的宽度,作为向右移动的最终的距离- 定位的作用:采用
left:xxpx
,来然按钮分开,第一个按钮是不需要移动的,left:0
完美的贴合在container
盒子的右侧,剩下了采用js计算一下,移动的比例(例如:第二个按钮,肯定需要在盒子滑出结束,定位在left:(第一个按钮width)px
)
滑动的总距离的时间 = 第二个按钮移到最终地点的时间 = 第三个按钮移到最终地点的时间 = .....
对象 | 所需时间 | 需滑动的距离 |
---|---|---|
总距离 | t | 按钮的总宽度 |
第二按钮 | t | 第一个按钮的宽度 |
我们需要得到x2这个距离,上几家按钮的宽度的总宽度.
假设一:向左滑动,当前总距离滑动的距离为 x1 ,第二按钮移动的距离为 x2
验证:x1滑动为总距离时,x2滑动了第一按钮的宽度。
得出:
我们能轻易的拿到x2的移动,与x1移动的关系了。
假设二:向右滑动,当前总距离滑动的距离为 -x1 ,第二按钮移动的距离为 x2
验证: 当X1=0为滑动时,X2第二按钮需要移动的距离是第一按钮的宽度。
得出:
拿到x2的移动,与x1移动的关系了。
有点点的弹性效果
- 弹性效果:都是有点溢出,在让其回归正常的一个过程动画。
开始
以上述的思路,开始先构建html与css,全部代码,最下面了,注意这是分析.
html的结构
<div class="swipe-cell">
<div class="swipe">
<div class="container">
内容
</div>
<div class="btn-right">
<div class="btn1 btn">标记已读</div>
<div class="btn2 btn">删除</div>
</div>
</div>
</div>
css的上色
/* 页面的处理 */
*{
box-sizing: border-box;
}
body{
margin: 0;
}
/* 滑动处的处理 */
.swipe-cell{
position: relative;
overflow: hidden;
}
.swipe-cell .swipe{
transform: translateX(0px); /* 初始滑动的距离 */
background-color: #eee;
}
/* 内容的处理 */
.container{
display: flex;
padding: 15px;
}
/* 按钮的处理 */
.btn-right{
position: absolute;
right: 0;
top: 0;
transform: translateX(100%);
height: 100%;
}
.btn{
position: absolute;
padding: 20px;
color: #fff;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
transition-duration: 0.25s;
transition-property: transform;
transition-timing-function: cubic-bezier(.18,.89,.32,1)
}
.btn1{
background-color: hotpink;
left: 0;
flex-wrap: nowrap;
white-space: nowrap;
}
.btn2{
background-color: #6a97eb;
left: 0;
white-space: nowrap;
}
js的运作
js的初始化
// 获取有用的节点
let swipeNode = document.querySelector(".swipe");
swipeNode.addEventListener("touchmove",touchMoveFuc,false) // 手指移动
swipeNode.addEventListener("touchend",touchEndFuc,false) // 手指结束触摸
let swipeContainer = swipeNode.querySelector(".container");
swipeContainer.addEventListener("touchstart",touchContainerStartFuc,false); // 手指开始触摸
let btnRightNode = item.querySelectorAll(".btn-right .btn");
//初始化数据
let startX = 0; // 初始用户点击的位置
let scrollDistance = 0; // 手指滑动的距离,上面说的x1
let swipeState = false; // false是没有滑动
let swipeContainerTimeId = null; // 定时 手指持续点击,就结束滑动状态的计时id
let scrollBtn = [];
let btnBlockTotalWidth = 0;
btnRightNode.forEach(itemNode =>{
// 节点 + 上几个节点的宽度 , 是为了计算上面的提到了一个比例
scrollBtn.push([itemNode,btnBlockTotalWidth])
btnBlockTotalWidth += itemNode.clientWidth;
})
js的监听事件,开始触摸
function touchContainerStartFuc(e){
startX = e.changedTouches[0].clientX; // 记录第一次触摸的
item.style.transitionDuration = "0s";
// 如果没有滑动swipeState=false,自然就只记录了,一次点击
// 如果没有滑动swipeState=true,自然就记录了,在规定的内如果没有移动就返回到滑动起始的地点
swipeContainerTimeId = setTimeout(()=>{
if(swipeState){
item.style.transitionDuration = "0.25s"; // 添加返回的动画时间
item.style.transform = `translateX(0px)`;
scrollDistance = 0;
clearTimeout(swipeContainerTimeId);
}
},500)
}
js的监听事件,移动手指
// 持续触摸中
function touchMoveFuc(e) {
// 你在规定的时间内移动,那么我就清除掉在开始触摸事件中留下的定时
clearTimeout(swipeContainerTimeId);
// 获取滑动距离
scrollDistance = startX - e.changedTouches[0].clientX;
if(!swipeState){
//正常拉出
if( scrollDistance>0){
//填充动态拉动效果
if(scrollDistance - btnBlockTotalWidth < 0){
// 拉出在正常范围内部
item.style.transform = `translateX(-${scrollDistance}px)`
// width是上一个块的,产生叠加的效果
// 使其同时结束,则width1+width2与width2走出了相同时间,width1+width2的长度与scrollDistance一样
for (let i = 0; i < scrollBtn.length - 1; i++) {
//记录这个btn前面的宽度,scrollDistance滑行到了btnBlockTotalWidt总宽,那么就是left前面宽度那么远
// 简单说,这个是百分比滑动的策略
scrollBtn[i+1][0].style.left = `${ (scrollDistance*scrollBtn[i+1][1])/btnBlockTotalWidth}px`
}
}else if(scrollDistance - btnBlockTotalWidth < 50){
// 多拉出一点,产生布局一种弹性效果
item.style.transform = `translateX(-${scrollDistance}px)`
btnRightNode.forEach(itemNode =>{
itemNode.style.paddingRight = `${20 + 50}px`
})
// 多拉出来一点 25的px对吧
for (let i = 0; i < scrollBtn.length - 1; i++) {
scrollBtn[i+1][0].style.left = `${ (scrollDistance*scrollBtn[i+1][1])/(btnBlockTotalWidth+25)}px`
}
}
}
}else{
//swipeState= true表示,已经拉出来了,可能下面要来回拉动了
scrollDistance = startX - e.changedTouches[0].clientX;
//滑出来的距离是 btnBlockTotalWidth
if(btnBlockTotalWidth + scrollDistance > 0){
if(scrollDistance < 0){
// scrollDistance是负数了,+就是-上滑动的scrollDistance,没有毛病,是上面分析的
item.style.transform = `translateX(${-(btnBlockTotalWidth+scrollDistance)}px)`
for (let i = 0; i < scrollBtn.length - 1; i++) {
scrollBtn[i+1][0].style.left = `${((btnBlockTotalWidth+scrollDistance)*scrollBtn[i+1][1])/(btnBlockTotalWidth)}px`
}
}else if(scrollDistance < 50){
// 多拉出一点,产生布局一种弹性效果
item.style.transform = `translateX(-${scrollDistance+btnBlockTotalWidth}px)`
btnRightNode.forEach(itemNode =>{
itemNode.style.paddingRight = `${20 + 50}px`
})
for (let i = 0; i < scrollBtn.length - 1; i++) {
scrollBtn[i+1][0].style.left = `${((btnBlockTotalWidth+scrollDistance)*scrollBtn[i+1][1])/(btnBlockTotalWidth+25)}px`
}
}
}
}
}
js监听事件,结束触摸
function touchEndFuc(e){
// 触摸结束
item.style.transitionDuration = "0.25s"; // 添加返回的动画时间
// 滑动一半以上,
// 还要swipeState = false
// 滑动的距离还要大于10,因为避免一开始触摸就松开,导致一些问题
if(btnBlockTotalWidth < scrollDistance*3 && !swipeState && scrollDistance>10){
console.log("触摸结束,已经滑出");
swipeState = true; //判定已经划出去了
item.style.transform = `translateX(${-btnBlockTotalWidth}px)`;
//展开,展开的距离就是离定位点的距离
for (let i = 0; i < scrollBtn.length - 1; i++) {
scrollBtn[i+1][0].style.left = `${(scrollBtn[i+1][1])}px`
}
}else{
// 不符合标准,就初始化,重新开始吧
swipeState = false;
item.style.transform = `translateX(0px)`;
scrollDistance = 0;
}
}
完整代码
HTML
<div class="swipe-cell">
<div class="swipe">
<div class="container">
<img src="./1.jpg" alt="道长">
<div class="info">
<div>红拂仙子</div>
<span>红拂仙子成为弃子,李化元密见令狐老祖有所图谋</span>
</div>
</div>
<div class="btn-right">
<div class="btn1 btn">标记已读</div>
<div class="btn2 btn">删除</div>
</div>
</div>
</div>
<div class="swipe-cell">
<div class="swipe">
<div class="container">
<img src="./2.jpg" alt="道长">
<div class="info">
<div>李化元</div>
<span>李化元,黄枫谷结丹修士,为人小气,三阳之体,修炼功法为真阳决,洞府叫绿波洞,韩立的师父。</span>
</div>
</div>
<div class="btn-right">
<div class="btn1 btn">标记已读</div>
<div class="btn2 btn">删除</div>
<div class="btn3 btn">黑名单</div>
</div>
</div>
</div>
CSS
*{
box-sizing: border-box;
}
body{
margin: 0;
}
.swipe-cell{
position: relative;
overflow: hidden;
}
.swipe-cell .swipe{
transform: translateX(0px);
background-color: #eee;
}
.container{
display: flex;
padding: 15px;
}
.container img{
height: 64px;
width: 64px;
border-radius: 50%;
margin-right: 8px;
}
.container .info{
flex-direction: column;
align-self: center;
font-size: 16px;
}
.container .info span{
display: inline-block;
font-size: 12px;
margin-top: 5px;
}
.btn-right{
position: absolute;
right: 0;
top: 0;
transform: translateX(100%);
height: 100%;
}
.btn{
position: absolute;
padding: 20px;
color: #fff;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
transition-duration: 0.25s;
transition-property: transform;
transition-timing-function: cubic-bezier(.18,.89,.32,1)
}
.btn1{
background-color: hotpink;
left: 0;
flex-wrap: nowrap;
white-space: nowrap;
}
.btn2{
background-color: #6a97eb;
left: 0;
white-space: nowrap;
}
.btn3{
background-color: #3a993f;
left: 0;
white-space: nowrap;
}
JavaScript
// 为了更好的移植性选择js
//右拉的弹性效果是怎么实现的
let swipeAllNode = document.querySelectorAll(".swipe");
swipeAllNode.forEach(item =>{
item.addEventListener("touchmove",touchMoveFuc,false)
item.addEventListener("touchend",touchEndFuc,false)
let swipeContainer = item.querySelector(".container");
swipeContainer.addEventListener("touchstart",touchContainerStartFuc,false);
let btnRightNode = item.querySelectorAll(".btn-right .btn");
let startX = 0; //初始用户点击的位置
let scrollDistance = 0; //用户滑动的距离
let scrollBtn = [];
let btnBlockTotalWidth = 0;
let swipeState = false;//false是没有滑动
let swipeContainerTimeId = null;
btnRightNode.forEach(itemNode =>{
// 节点 + 上几个节点的宽度
scrollBtn.push([itemNode,btnBlockTotalWidth])
btnBlockTotalWidth += itemNode.clientWidth;
})
function touchContainerStartFuc(e){
startX = e.changedTouches[0].clientX;
item.style.transitionDuration = "0s";
swipeContainerTimeId = setTimeout(()=>{
if(swipeState){
item.style.transitionDuration = "0.25s"; // 添加返回的动画时间
item.style.transform = `translateX(0px)`;
scrollDistance = 0;
clearTimeout(swipeContainerTimeId);
}
},500)
}
function touchMoveFuc(e) {
// 持续触摸中
clearTimeout(swipeContainerTimeId);
scrollDistance = startX - e.changedTouches[0].clientX;
if(!swipeState){
//正常拉出
if( scrollDistance>0){
//填充动态拉动效果
if(scrollDistance - btnBlockTotalWidth < 0){
// 拉出在正常范围内部
item.style.transform = `translateX(-${scrollDistance}px)`
// width是上一个块的,产生叠加的效果
// 使其同时结束,则width1+width2与width2走出了相同时间,width1+width2的长度与scrollDistance一样
for (let i = 0; i < scrollBtn.length - 1; i++) {
//记录这个btn前面的宽度,scrollDistance滑行到了btnBlockTotalWidt总宽,那么就是left前面宽度那么远
// 简单说,这个是百分比滑动的策略
scrollBtn[i+1][0].style.left = `${ (scrollDistance*scrollBtn[i+1][1])/btnBlockTotalWidth}px`
}
}else if(scrollDistance - btnBlockTotalWidth < 50){
// 多拉出一点,产生布局一种弹性效果
item.style.transform = `translateX(-${scrollDistance}px)`
btnRightNode.forEach(itemNode =>{
itemNode.style.paddingRight = `${20 + 50}px`
})
for (let i = 0; i < scrollBtn.length - 1; i++) {
scrollBtn[i+1][0].style.left = `${ (scrollDistance*scrollBtn[i+1][1])/(btnBlockTotalWidth+25)}px`
}
}
}
}else{
//来回拉动
scrollDistance = startX - e.changedTouches[0].clientX;
//滑出来的距离是 btnBlockTotalWidth
if(btnBlockTotalWidth + scrollDistance > 0){
if(scrollDistance < 0){
item.style.transform = `translateX(${-(btnBlockTotalWidth+scrollDistance)}px)`
for (let i = 0; i < scrollBtn.length - 1; i++) {
scrollBtn[i+1][0].style.left = `${((btnBlockTotalWidth+scrollDistance)*scrollBtn[i+1][1])/(btnBlockTotalWidth)}px`
}
}else if(scrollDistance < 50){
// 多拉出一点,产生布局一种弹性效果
item.style.transform = `translateX(-${scrollDistance+btnBlockTotalWidth}px)`
btnRightNode.forEach(itemNode =>{
itemNode.style.paddingRight = `${20 + 50}px`
})
for (let i = 0; i < scrollBtn.length - 1; i++) {
scrollBtn[i+1][0].style.left = `${((btnBlockTotalWidth+scrollDistance)*scrollBtn[i+1][1])/(btnBlockTotalWidth+25)}px`
}
}
}
}
}
function touchEndFuc(e){
// 触摸结束
item.style.transitionDuration = "0.25s"; // 添加返回的动画时间
// 滑动一半以上,
// 还要swipeState = false
// 滑动的距离还要大于10,因为避免一开始触摸就松开,导致一些问题
if(btnBlockTotalWidth < scrollDistance*3 && !swipeState && scrollDistance>10){
console.log("触摸结束,已经滑出");
swipeState = true; //判定已经划出去了
item.style.transform = `translateX(${-btnBlockTotalWidth}px)`;
//展开,展开的距离就是离定位点的距离
for (let i = 0; i < scrollBtn.length - 1; i++) {
scrollBtn[i+1][0].style.left = `${(scrollBtn[i+1][1])}px`
}
}else{
// 不符合标准,就初始化,重新开始吧
swipeState = false;
item.style.transform = `translateX(0px)`;
scrollDistance = 0;
}
}
})