前端页面双向滚动方案

简介: 脱离canvas后,页面如何实现上下左右双滑动?又如何在安卓系统和iOS系统上实现?

原创 安笺 淘系技术 6月29日

640.gif


脱离canvas后,页面如何实现上下左右双滑动?又如何在安卓系统和iOS系统上实现?



背景


在许多业务场景中会遇到让一个页面在web端可以实现双向滚动,且在滚动的时候可以上部吸顶,左侧吸在最左边这中类似excel表现的述求。
但是excel移动端本身使用canvas来画的,所以能保持较好的体验和性能,那么脱离canvas,我们是否有别的方案可以去靠近实现呢?如何在移动端实现,并同时兼容安卓和ios系统?

实现目标


如下图所以,可以左右移动,也支持上下移动,在移动的对应方向的顶部需要吸顶。在pc端实现较为简单,通过滚动就可以实现,在移动端要想实现这一套方案,并同时兼容不同系统,这里我踩了一些雷,下文将诉说实现这个的全部技术方案的改变和踩雷的一些点。


640 (1).gif

技术方案


  初期思想

image.png


如上图所示,不就是横向滚动和竖向默认滚动吗?


移动端屏幕宽高固定,本就可以左右滚动,那么只需要元素本身的大小大于屏幕的宽高不就可以了吗?


设置一个div,宽大于screen.width, 高大于screen.height即可,最终demo大概效果如下图所示:


640 (2).gif

左右的确都相当的丝滑,能横着滚,也能竖着滚了,就是全方位也都能滚动,着实诧异了一会儿。

  更新思维


如果那么当我朝着某个方向滚动的时候,禁掉一个方向的滚动,就可以解决了。


但是容器嵌套就出问题了,横向滚动的时候,我们领容器高度变成视口高度,overflow-y设置为hidden,横向依旧可以滚动,纵向设置为禁止滚动,在chrome里模拟了一下,的确不错,在真机上测试的时候,发现安卓端正常,ios端一旦手动设置overflow-y,发现纵向原先滚动到的位置会丢失,纵向内容直接回到最顶部,即纵向scrollTop变成了0。


设置纵向滚动,横向overflow-x为hidden时,横向原先滚动的距离也会被清除,回到最左侧。


那么通过手动记录之前的滚动位置,在设置某一方向禁止滚动时,把高度给他还原回去,就能解决了。


思路是对的,但是会出现强烈的闪屏现象,尤其在这种数据密集型的页面上(ios页面也闪,低端安卓机没做尝试,meta30存在一定延迟的闪屏)。


  改变方案

双向滚动到此基本上算是失败了。


单向滚动肯定没问题,另一个方向我们可以使用模拟滚动的方式来自己写一套"滚动"效果。


首先我们判断横向滚动和纵向滚动那一方向使用原生滚动呢?


我选择了纵向,纵向数据体量大,容易有滚动的诉求,横向存在数据量不足可能不需要滚动。


效果如下图所示:

640 (3).gif

image.gif

左侧第一列和顶部第一行吸顶这里使用了css的属性 position:sticky。


也有人可以考虑使用一些特殊布局,例如左侧做绝对定位,右侧内容自行滚动等。


模拟滚动的方式,我们采用touch事件来仿造,通过touchStart、touchMove、touchEnd 这3个事件来模拟scroll事件。


首先在touchStart中获取手指头接触到屏幕的点的 (x,y) 坐标,然后在touchMove事件中获取屏幕出点的第二个触点的(x,y)坐标,来获取触摸方向。



//获得角度
function getAngle(angx, angy) {
  return Math.atan2(angy, angx) * 180 / Math.PI;
};
function getDirection(startx, starty, endx, endy) {
  const angx = endx - startx;
  const angy = endy - starty;
  let result = 0;
  //如果滑动距离太短
  if (Math.abs(angx) < 2 && Math.abs(angy) < 2) {
    return result;
  }
  const angle = getAngle(angx, angy);
  if (angle >= -150 && angle <= -30) {
    return 'top';
  } else if (angle > 30 && angle < 150) {
    return 'down';
  } else if ((angle >= 150 && angle <= 180) || (angle >= -180 && angle < -150)) {
    return 'left';
  } else if (angle >= -30 && angle <= 30) {
    return 'right'
  }
}


由于大家在使用手机时,横向滑动的行为轨迹并非是一个水平180度的横线,所以在这里设定了一定值,例如多少角度到多少角度认为是横向滚动。


方向定好,多少角度这个可以根据自己业务上的判断做修改。


image.pngimage.gif

连续触摸滑动的坐标轴我们已经能拿到了,通过css3的transform来使元素进行移动,通过不断修改移动的值达到元素滚动的效果。


这里有一点需要注意,并非我们触摸划过多少像素,元素就移动多少像素,这里还涉及一个速度和距离的概念。


还有我们手指头松开以后,元素应该还会滚动一部分,然后速度慢慢降下来,直到0为止。


先分析一直处于触点滑动这个阶段,我们并非划过多少像素,元素就跟着滚动多少像素,这里看情况来放大或者缩小这里的滚动值,体现给用户的感受就是滑的快还是划得慢。


我们由于横向数据量并不大,所以这里我们所有滚动距离都做了一定缩小处理。



// 代码只留个框架
function rowScrollAction(scrollDirection, endx, isEnding = false) {
    scrollGap = (endx - startx) * 0.85; // 每个滚动距离都乘上0.85
    let scrollLen = preX + scrollGap; // preX为之触摸之前已经滚动到的位置
    if (isEnding) {
      scrollLen = endx
    }
    if (scrollDirection === 'right') {
      ....
    }
    if (scrollDirection === 'left') {
     ....
    }
  }


这里还需要注意的点是,如果已经滚动到横向边界了,那么就不允许在滚动了,这里需要特殊处理一下。


那么触摸结束以后,元素还应该按照惯性继续向对应方向进行滚动,速度慢慢下降,直到结束为止。


速度不能直线下降,不能是一次函数,因为:y=ax+b


a代表加速度,加速的值一直不变,那么就会匀速降下去,体验上并不好,这里选择了二次函数的概念。


image.gifimage.png

如图所示,慢慢靠近目标值,那么就要口子朝下的抛物线,且是对称轴左侧的这一部分函数。根据函数定义:


image.pngimage.gif

通过a、b参数来产出一个对应计算函数:



// 将"当前时间"映射[0, 1]间(currentTime /= duration)
// 根据缓入公式计算出"当前时间"应该移动的百分比(currentTime * currentTime)
// 根据"总移动长度"和"移动百分比"算出应移动的具体值(changeValue * ...)
// 加上初始位置(+ startValue)
function easeOut(currentTime, startValue, changeValue, duration) {
  currentTime /= duration;
  return -changeValue * currentTime * (currentTime - 2) + startValue;
}


本业务场景中的实现代码如下:



/**
*  target: 滚动最终像素值
*/
const scrollAnimation = (target) => {
    function easeOut(t, b, c, d) {
      return -c * (t /= d) * (t - 2) + b; // 用时间t做x变量
    }
    var toTarget = target;
    var endTimer = 500;
    var startTimer = 0;
    var step = function () {
      let value = easeOut(startTimer, preX, toTarget, endTimer); // 返回一个距离值
      // rowScrollAction(tempDirection, value, true); 执行对应元素滚动多少像素的方法 
      startTimer += 25;
      if (startTimer <= endTimer) {
        // 继续运动
        requestAnimationFrame(step);
      } else {
        // 动画结束
        touchTimes = 0;
        // 动画结束后的回调
      }
    };


当滚动到边界时,记得清空循环计算哦


  最后优化


当横向滚动到边界时,模拟ios的弹簧效果(安卓和ios端保持一致)


640 (4).gif

实现方式就是对3.3中的滚动到边界时做对应的处理。我们达到边界以后,在滚动距离上多加50~100个像素值,然后利用抛物线的原理:


image.png


利用两边的值,先慢慢靠近最高点,然后再回到对应y值上。


结论


接手一个项目,要严谨一点,如果是之前没接触过这种技术方案开发的,应该拿到项目时先写个小demo,然后再去评估开发时间。


不然脑海中出现的技术方案可以让你当场拍板,但是在实践中会发现出现预估错误,可是开发排期影响的就是整个小team的时间,你的delay很可能导致大家的排期都被打乱,不得不从拼命加班抢回开发时间。


还要学会给自己留充足的buffer能够去试错,能够应对突发情况。

相关文章
|
14天前
|
前端开发 JavaScript 开发者
前端 CSS 优化:提升页面美学与性能
前端CSS优化旨在提升页面美学与性能。通过简化选择器(如避免复杂后代选择器、减少通用选择器使用)、合并样式表、合理组织媒体查询,可减少浏览器计算成本和HTTP请求。利用硬件加速和优化动画帧率,确保动画流畅。定期清理冗余代码并使用缩写属性,进一步精简代码。这些策略不仅加快页面加载和渲染速度,还提升了视觉效果,为用户带来更优质的浏览体验。
|
2月前
|
JavaScript 前端开发 程序员
前端原生Js批量修改页面元素属性的2个方法
原生 Js 的 getElementsByClassName 和 querySelectorAll 都能获取批量的页面元素,但是它们之间有些细微的差别,稍不注意,就很容易弄错!
|
4天前
|
Dart 前端开发 Android开发
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
2月前
|
前端开发 数据安全/隐私保护
.自定义认证前端页面
.自定义认证前端页面
19 1
.自定义认证前端页面
|
2月前
|
前端开发 JavaScript 搜索推荐
前端懒加载:提升页面性能的关键技术
前端懒加载是一种优化网页加载速度的技术,通过延迟加载非首屏内容,减少初始加载时间,提高用户访问体验和页面性能。
|
2月前
|
前端开发 安全 JavaScript
在阿里云快速启动Appsmith搭建前端页面
本文介绍了Appsmith的基本信息,并通过阿里云计算巢完成了Appsmith的快速部署,使用者不需要自己下载代码,不需要自己安装复杂的依赖,不需要了解底层技术,只需要在控制台图形界面点击几下鼠标就可以快速部署并启动Appsmith,非技术同学也能轻松搞定。
|
2月前
|
前端开发 数据可视化 搜索推荐
深入剖析极态云优雅的前端框架设计方案(上)
最近在体验极态云,这款低代码软件开发产品,发现其前端框架设计方案很优雅很强大! 在接下来的学习过程中,我将持续输出自己对极态云前端框架设计方案的深入理解,包括具体的使用技巧、优势分析以及可能的应用场景等方面的内容,希望能为大家提供有价值的参考。
|
3月前
|
前端开发 JavaScript
回顾前端页面发送ajax请求方式
回顾前端页面发送ajax请求方式
47 18
|
4月前
|
前端开发 JavaScript API
前端JS读取文件内容并展示到页面上
前端JavaScript使用FileReader API读取文件内容,支持文本类型文件。在文件读取成功后,可以通过onload事件处理函数获取文件内容,然后展示到页面上。
150 2
前端JS读取文件内容并展示到页面上
|
3月前
|
前端开发 数据安全/隐私保护
angular前端基本页面验证
angular前端基本页面验证
40 1

热门文章

最新文章