首页 > 其他分享 >使用原生js 写的picker 效果

使用原生js 写的picker 效果

时间:2023-10-31 23:47:15浏览次数:28  
标签:picker 原生 return index js options offset const event

class Picker {   DEFAULT_DURATION = 200;   MIN_DISTANCE = 10;   DEMO_DATA = [];   // demo数据   // 惯性滑动思路:   // 在手指离开屏幕时,如果和上一次 move 时的间隔小于 `MOMENTUM_LIMIT_TIME` 且 move   // 距离大于 `MOMENTUM_LIMIT_DISTANCE` 时,执行惯性滑动   MOMENTUM_LIMIT_TIME = 30;   MOMENTUM_LIMIT_DISTANCE = 15;   supportsPassive = false;
  constructor(options = {}) {     this.initValue(options);     this._initValue(options);     this.resetTouchStatus();     this.initComputed(options);     this.setEleStyle();
    this.onMounted();   }
  // 私有变量   _initValue(options) {     this.offset = 0;     this.duration = 0;     this.options = this.initOptions;     this.direction = options.direction || "vertical";     this.deltaX = 0;     this.deltaY = 0;     this.offsetX = 0;     this.offsetY = 0;
    this.startX = 0;     this.startY = 0;
    this.moving = false;     this.startOffset = 0;
    this.transitionEndTrigger = null; // 滚动函数     this.touchStartTime = 0; // 记录开始滑动时间     this.momentumOffset = 0; // 记录开始滑动位置
    this.currentIndex = this.defaultIndex;   }
  // 初始化--用户变量   initValue(options) {     // 可是区域子元素个数     this.visibleItemCount = Number(options.visibleItemCount || 3) || 3;     // 子元素高度     this.itemPxHeight = Number(this.itemPxHeight) || 105;     // 初始化传入的数据列表(当前案例微用到,可结合框架使用)     this.initOptions = options.initOptions || this.DEMO_DATA;     // 是否只读     this.readonly = options.readonly || false;     // 初始显示元素(当前案例未使用,可结合框架扩展)     this.defaultIndex = Number(options.defaultIndex) || 0;   }
  // 根据传入变量--获取计算属性   initComputed(options) {     // 外层容器高度     this.wrapHeight = this.itemPxHeight * this.visibleItemCount;     this.maskStyle = {       backgroundSize: `100% ${(this.wrapHeight - this.itemPxHeight) / 2}px`,     };     this.frameStyle = { height: `${this.itemPxHeight}px` };
    // this.count = this.options.length     this.count = document.querySelector(       ".bo-wrapper-container"     ).children.length;     this.baseOffset = (this.itemPxHeight * (this.visibleItemCount - 1)) / 2;     // 内层元素高度计算     this.wrapperStyle = {       transform: `translate3d(0, ${this.offset + this.baseOffset}px, 0)`,       transitionDuration: `${this.duration}ms`,       transitionProperty: this.duration ? "all" : "none",     };   }
  // 设置外部容器的样式及遮罩层   setEleStyle() {     let mask = document.querySelector(".bo-mask");     let coverBorder = document.querySelector(".bo-cover-border");     let columnItem = document.querySelectorAll(".bo-column-item");     mask.style.backgroundSize = this.maskStyle.backgroundSize;     coverBorder.style.height = this.frameStyle.height;     this.setUlStyle();     this.setColumnHeight(columnItem);   }
  // 滑动主要逻辑--动态设置容器的垂直方向偏移量   setUlStyle() {     let wrapperContainer = document.querySelector(".bo-wrapper-container");     wrapperContainer.style.transform = this.wrapperStyle.transform;     wrapperContainer.style.transitionDuration =       this.wrapperStyle.transitionDuration;     wrapperContainer.style.transitionProperty =       this.wrapperStyle.transitionProperty;   }
  setUlTransform() {     this.initComputed();     this.setUlStyle();   }
  // 设置每个行元素的高度及点击事件   setColumnHeight(columnItem) {     columnItem.forEach((item, index) => {       item.style.height = `${this.itemPxHeight}px`;       item.tabindex = index;       item.onclick = () => {         this.onClickItem(index);         this.setUlTransform();       };     });   }
  // 点击单个行元素   onClickItem(index) {     if (this.moving || this.readonly) {       return;     }
    this.transitionEndTrigger = null;     this.duration = this.DEFAULT_DURATION;     this.setIndex(index, true);   }
  // 初始化完成--执行事件绑定   onMounted() {     let el = document.querySelector(".bo-picker-column");     this.bindTouchEvent(el);     this.bindMouseScrollEvent(el); // 添加鼠标滚轮事件   }
  bindTouchEvent(el) {     const { onTouchStart, onTouchMove, onTouchEnd, onTransitionEnd } = this;     let wrapper = document.querySelector(".bo-wrapper-container");
    this.on(el, "touchstart", onTouchStart);     this.on(el, "touchmove", onTouchMove);     this.on(wrapper, "transitionend", onTransitionEnd);
    if (onTouchEnd) {       this.on(el, "touchend", onTouchEnd);       this.on(el, "touchcancel", onTouchEnd);     }   }
  on(target, event, handler, passive = false) {     target.addEventListener(       event,       handler,       this.supportsPassive ? { capture: false, passive } : false     );   }
  // 动画结束事件   onTransitionEnd = () => {     this.stopMomentum();   };
  // 滑动结束后数据获取及优化处理   stopMomentum() {     this.moving = false;     this.duration = 0;
    if (this.transitionEndTrigger) {       this.transitionEndTrigger();       this.transitionEndTrigger = null;     }   }
  // 开始滑动   onTouchStart = (event) => {     // 控制只读     if (this.readonly) return;     let wrapper = document.querySelector(".bo-wrapper-container");     this.touchStart(event);
    if (this.moving) {       const translateY = this.getElementTranslateY(wrapper);       this.offset = Math.min(0, translateY - this.baseOffset);       this.startOffset = this.offset;     } else {       this.startOffset = this.offset;     }
    this.duration = 0;     this.transitionEndTrigger = null;     this.touchStartTime = Date.now();     this.momentumOffset = this.startOffset;
    // 设置滑动     this.setUlTransform();   };
  touchStart(event) {     this.resetTouchStatus();     this.startX = event.touches[0].clientX;     this.startY = event.touches[0].clientY;   }
  // 重置滑动数据变量   resetTouchStatus() {     this.direction = "";     this.deltaX = 0;     this.deltaY = 0;     this.offsetX = 0;     this.offsetY = 0;   }
  // 动态获取元素滑动距离--关键   getElementTranslateY(element) {     const style = window.getComputedStyle(element);     const transform = style.transform || style.webkitTransform;     const translateY = transform.slice(7, transform.length - 1).split(", ")[5];     return Number(translateY);   }
  onTouchMove = (event) => {     if (this.readonly) return;
    this.touchMove(event);
    if (this.direction === "vertical") {       this.moving = true;       this.preventDefault(event, true);     }
    this.offset = this.range(       this.startOffset + this.deltaY,       -(this.count * this.itemPxHeight),       this.itemPxHeight     );
    const now = Date.now();     if (now - this.touchStartTime > this.MOMENTUM_LIMIT_TIME) {       this.touchStartTime = now;       this.momentumOffset = this.offset;     }
    // 滑动中     this.setUlTransform();   };
  onTouchEnd = (event) => {     if (this.readonly) return;
    const distance = this.offset - this.momentumOffset;     const duration = Date.now() - this.touchStartTime;     const allowMomentum =       duration < this.MOMENTUM_LIMIT_TIME &&       Math.abs(distance) > this.MOMENTUM_LIMIT_DISTANCE;
    if (allowMomentum) {       this.momentum(distance, duration);       return;     }
    const index = this.getIndexByOffset(this.offset);     this.duration = this.DEFAULT_DURATION;     this.setIndex(index, true);
    // 滑动结束     this.setUlTransform();
    // compatible with desktop scenario     // use setTimeout to skip the click event triggered after touchstart     setTimeout(() => {       this.moving = false;     }, 0);   };
  // 滑动动画函数--关键   momentum(distance, duration) {     const speed = Math.abs(distance / duration);
    distance = this.offset + (speed / 0.003) * (distance < 0 ? -1 : 1);
    const index = this.getIndexByOffset(distance);
    this.duration = +this.swipeDuration;     this.setIndex(index, true);   }
  // 获取当前展示的元素数据信息--关键   setIndex(index, emitChange) {     index = this.adjustIndex(index) || 0;
    const offset = -index * this.itemPxHeight;
    const trigger = () => {       if (index !== this.currentIndex) {         this.currentIndex = index;
        if (emitChange) {           // this.$emit('change', index);           console.log(index);         }       }     };
    // trigger the change event after transitionend when moving     if (this.moving && offset !== this.offset) {       this.transitionEndTrigger = trigger;     } else {       trigger();     }
    this.offset = offset;   }
  getValue() {     return this.options[this.currentIndex];   }
  adjustIndex(index) {     index = this.range(index, 0, this.count);
    for (let i = index; i < this.count; i++) {       if (!this.isOptionDisabled(this.options[i])) return i;     }
    for (let i = index - 1; i >= 0; i--) {       if (!this.isOptionDisabled(this.options[i])) return i;     }   }
  isOptionDisabled(option) {     return this.isObject(option) && option.disabled;   }
  isObject(val) {     return val !== null && typeof val === "object";   }
  // 滑动偏移量   getIndexByOffset(offset) {     return this.range(       Math.round(-offset / this.itemPxHeight),       0,       this.count - 1     );   }
  // 阻止默认行为   preventDefault(event, isStopPropagation) {     /* istanbul ignore else */     if (typeof event.cancelable !== "boolean" || event.cancelable) {       event.preventDefault();     }
    if (isStopPropagation) {       this.stopPropagation(event);     }   }
  stopPropagation(event) {     event.stopPropagation();   }
  touchMove(event) {     const touch = event.touches[0];     this.deltaX = touch.clientX - this.startX;     this.deltaY = touch.clientY - this.startY;     this.offsetX = Math.abs(this.deltaX);     this.offsetY = Math.abs(this.deltaY);     this.direction =       this.direction || this.getDirection(this.offsetX, this.offsetY);   }
  // 确定滑动方向   getDirection(x, y) {     if (x > y && x > this.MIN_DISTANCE) {       return "horizontal";     }
    if (y > x && y > this.MIN_DISTANCE) {       return "vertical";     }
    return "";   }
  // 滑动范围限制--关键代码   range(num, min, max) {     return Math.min(Math.max(num, min), max);   }
  bindMouseScrollEvent(el) {     let isFirefox = typeof InstallTrigger !== "undefined"; // Firefox 1.0+     let mouseScrollEvent = isFirefox ? "DOMMouseScroll" : "mousewheel"; // 检测是否是火狐浏览器,如果是则使用"DOMMouseScroll"事件     const handler = (event) => {       event.preventDefault(); // 防止默认滚动行为       let delta = isFirefox ? -event.detail : event.wheelDelta; // Firefox 和 其他浏览器滚动值是相反的,修改以保持一致性。
      // 使用滚轮的滚动值更新 currentIndex 的值,并在改变选择项后重新渲染视图       this.currentIndex += delta > 0 ? -1 : 1;       this.onClickItem(this.currentIndex);       this.setUlTransform();     };
    this.on(el, mouseScrollEvent, handler);   } } new Picker()

标签:picker,原生,return,index,js,options,offset,const,event
From: https://www.cnblogs.com/coodeshark/p/17802016.html

相关文章

  • 【闭包应用】JS:防抖、节流
    1、防抖:当进行连续操作时,只执行最后一次的操作。//防抖的概念是当进行连续操作时,只执行最后一次的操作。functiondebounce(fn,delayTime){lettimeout=null;returnfunction(){if(timeout){clearTimeout(timeout);......
  • 软件测试|Python对JSON的解析和创建详解
    简介JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式,已经成为当今互联网应用中广泛使用的数据格式之一。Python提供了内置的模块来解析和创建JSON数据,使得在Python中处理JSON变得非常简单。本文将详细介绍Python对JSON的解析和创建过程,并提供示例代码来帮助大家更好......
  • js中字符串使用单引号还是双引号
    ES6如下描述:字符串静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。//badconsta="foobar";constb='foo'+a+'bar';//acceptableconstc=`foobar`;//goodconsta='foobar';constb=`foo${a}bar`;......
  • Node.js子进程:你想要知道的一切
    如何使用spawn(),exec(),execFile()和fork()对于单进程而言,Node.js的单线程和非阻塞特性表现地非常好。然而,对于处理功能越来越复杂的应用程序而言,一个单进程的CPU是远远无法满足需要的。无论你的服务器有多强大,单线程都是远远不够用的。事实上,Node.js的单线程特性......
  • SIP UserAgent (B2BUA client)——pjsip
    1.sipstackspjsip/bell-sip/sofia-sip/libeXosip/librehttps://github.com/staskobzar/sip_stacks_examples 2.sipuseragentandservernetworkarchitecture3. InstallingpjsiponUbuntuhttps://www.pjsip.orgsudoapt-getinstalllibasound2-devLinuxsys......
  • js实现web端批量下载
    使用场景:比如一个展现pdf文档列表的网页内需要允许客户自己勾选(可多选)需要打包下载的内容,最终以zip文件的形式在浏览器下载此功能也可在服务端实现,但是在web端实现可以降低服务器负载,提升客户体验。下面是代码<!DOCTYPEhtml><html><headlang="en"><metacharset="UTF-8">......
  • PasteSpider之appsettings.json中的Serilog的配置,分流不同日志层级的信息!
    在实际使用Serilog中,我们通常会有不一样的需求,常见的比如1.按照等级,高级哪个等级的才记录2.记录文件每个多大,超过的划分到下一个文件中3.不同等级的记录到不同的位置中4.按照不一样的格式输出以下是PasteSpider中的appsettings.json中关于Serilog的配置"Serilog":{......
  • 【面试题】你理解中JS难理解的基本概念是什么?
    作用域与闭包作用域作用域是当前的执行上下文,值和表达式在其中“可见”或可被访问。如果一个变量或表达式不在当前的作用域中,那么它是不可用的。作用域也可以堆叠成层次结构,子作用域可以访问父作用域,反过来则不行。————MDN作用域最重要的特点是:子作用域可以访问父作用域,反之则......
  • mysql处理json格式的字段,一文搞懂mysql解析json数据
    文章目录一、概述1、什么是JSON2、MySQL的JSON3、varchar、text、json类型字段的区别二、JSON类型的创建1、建表指定2、修改字段三、JSON类型的插入1、字符串直接插入2、JSON_ARRAY()函数插入数组3、JSON_OBJECT()函数插入对象4、JSON_ARRAYAGG()和JSON_OBJECTAGG()将查询结果封装......
  • 面向Three.js开发者的3D自动纹理化开发包
    DreamTexture.js是面向three.js开发者的 3D模型纹理自动生成与设置开发包,可以为webGL应用增加3D模型的快速自动纹理化能力。图一为原始模型,图二图三为贴图后的模型。提示词:city,Realistic,cinematic,Frontview,Gamescenegraph1、DreamTexture.js开发包内......