爱短文
名人言
爱诗词
神评论
毒鸡汤
博 客
一篇文字
一篇文字
@ 吴掌柜
2978968560@qq.com
一篇文字博客,记录着姓吴的他工作上遇到的解决的各种坑,既然是个人博客嘛!简简单单的就好。
微博
6635
0
js 文档
> **storagex-js 1.4.9** [源码](https://github.com/wurencaideli/storagex-js) ## 介绍 storagex-js是什么? 在开发web网页的时候,难免会操作本地缓存数据,最常用的就是localStorage,sessionStorage,在uniapp中的uniStorage,在微信小程序中的wxStorage。而storagex-js是用对象化代理来简化操作本地数据的,举个简单的例子。 ```javascript //使用localStorage localStorage.setItem('test',123); //写入数据 let test = localStorage.getItem('test',123); //获取数据 //使用storagex-js let localServe = localStorageX(); localServe.test = 123; //写入数据 let test = localServe.test //获取数据 ``` 这是一个简单的使用方法,操作本地数据就跟操作一个对象一样简单, 但是这样也方便不到那里去,storagex-js方便的是对数据深层次的访问操作。下面开始使用。 ## 安装和导入 ```js //使用npm安装 npm install storagex-js ``` ```javascript //需要什么就导入什么 (所有方法皆是同步方法) import { storageX, //根据模式生成代理对象 参数(key,target,mode) StorageX, //参数({key,target,mode}) localStorageX, //生成localStorage代理对象 参数(key,target) LocalStorageX, //参数({key,target}) sessionStorageX, SessionStorageX, uniStorageX, UniStorageX, wxStorageX, WxStorageX, handleTasks, //执行所有的任务 deepRemoveProxy, //深度取消代理写入 参数(target) removeStorageItem, //根据键名清除本地数据 参数(key,mode) } from "storagex-js"; ``` ## 参数定义,以及参数名及意义 参数名及含义 1. key : 写入本地数据的键名 2. target : 代理对象(就是需要保存在本地的初始化数据对象) 3. mode : 模式(可选值 local,session,uni,wx) 模式的含义 local: localStorage,session: sessionStorage,uni: uniStorage,wx: wxStorage ## 使用storageX() 方法 > 参数 key,target,mode 生成代理对象,对代理对象的操作会相应的将该代理的对象储存到本地,操作该代理对象有两种模式 ##### 1:只是对键值进行操作 ```javascript const storageServe= storageX(undefined,undefined,'local'); storageServe.test = 123; //localStorage 'test'键 对应的值 '123' storageServe.a = {b:1}; //localStorage 'a'键 对应的值 {'b':'1'} storageServe.a.b = 2 //无效,不允许深度修改 //需要这样 storageServe.a = {b:2}; ``` ##### 2:深度的对象化存储(单纯的只对值进行操作) ```javascript //因为考虑到如果需要深度代理那么值一定是个对象 let state = { //需要初始化的值 a:1, b:{ c:3, }, }; const storageServe= storageX('state',state,'local'); storageObj.a = 2; storageObj.a = {b:{c:{d:2}}}; storageObj.a.c.d = 3; console.log(storageObj.a.c.d); //打印 3 //只针对值为对象时,而且只会对该对象属性进行修改添加,也就是说像下面这样不管用 storageObj = null; storageObj = 1; storageObj = {a:1}; //如果想修改键的值请使用上面的那种方式。也就是说,该方式永远只会对 储存中的'test'键的值进行操作。 //如果该 'test'键 中本来就有一个对象则以该对象为代理对象。 ``` ## 使用StorageX(跟storageX方法一样,只是创建方法不同) ```javascript const storageServe= new StorageX({ key:'state', state:state, mode:'local', }); ``` ## 使用localStorageX > 参数 key,target 相比storageX方法而言呢只是少了一个mode的参数,根据语义可以判断出,该方法操作的数据是保存在localStorage储存中的。 ##### 1:只是对键值进行操作 ```javascript const storageServe= localStorageX(); storageServe.test = 123; //localStorage 'test'键 对应的值 '123' storageServe.a = {b:1}; //localStorage 'a'键 对应的值 {'b':'1'} storageServe.a.b = 2 //无效,不允许深度修改 //需要这样 storageServe.a = {b:2}; ``` ##### 2:深度的对象化存储(单纯的只对值进行操作) ```javascript //因为考虑到如果需要深度代理那么值一定是个对象 let state = { //需要初始化的值 a:1, b:{ c:3, }, }; const storageServe= localStorageX('state',state); storageObj.a = 2; storageObj.a = {b:{c:{d:2}}}; storageObj.a.c.d = 3; console.log(storageObj.a.c.d); //打印 3 ``` ##### 3:删除 ```javascript //参数 key LocalStorageX.removeItem('state'); ``` ## handleTasks 的使用方法(执行任务列表里的所有任务) 如果是深度代理的话频繁操作数据则会创建多个任务且取最后的一个任务,跟普通的防抖函数类似。 ```javascript //导入后直接使用 handleTasks(); ``` ## deepRemoveProxy 的使用方法(清除已有的代理写入) ```javascript //参数 target deepRemoveProxy(target); ``` ## 其他方法 其他几种模式的方法都差不多了,这里就不一一赘述了。 皆不支持函数式储存,因为函数无法JSON化,既然是数据的话为什么要存函数呢? 可能遇到的问题:因为深度对象化代理难免遇到大数据量的循环修改,每修改一次就存一次太消耗性能,所以一次任务有多次修改的话,那么最多会存一次,就是创建了一个宏任务等待被执行后保存(跟js写的防抖函数类似)。 ### 小妙招 有时会遇到这样的情况,当本地数据对象需要再加一个键时,而StorageX会使用储存中已有的值,所以需要清空缓存在刷新一遍,其实不需要这么做。看下面的例子 ```javascript let state = { a:1, }; const storageServe= localStorageX('state',state); storageObj.a = 2; console.log(storageObj.a); //打印 2 ``` 修改state ```javascript let state = { a:1, b:3, }; const storageServe= localStorageX('state',state); console.log(storageObj.b); //打印 undefind ``` 改进 ```javascript let state = { a:1, b:3, }; const storageServe= localStorageX('state',state); for(let index in state){ if(storageServe.hasOwnProperty(index)) continue; storageServe[index] = state[index]; } console.log(storageObj.b); //打印 3 ``` ## 开发日志 #### 1.4.8 优化。 #### 1.4.6 优化 代理对象属性值发生改变只触发一个写入函数,也就是说如果一个代理对象的一个属性值是一个对象且赋值到另一个代理对象上,则该对象修改属性值时只会触发后者代理对象写入本地,之前的就不管用了。建议从之前的代理对象上重新读取一次(读取时会重新分配写入函数)。 #### 1.4.5 优化 在使用过程中已加入代理的直接写入,已放弃的代理对象使用 deepRemoveProxy 方法深层清除代理写入。 #### 1.4.4 BUG(已修复) 使用过程中的性能问题,之前监听的时候每次都会获取本地数据进行判断该键是否被手动删除,发现这样会有性能问题,现在已经删除。修改了写入数据的防抖设置,之前是执行第一次和最后一次,现在是只执行最后一次,性能最重要。 #### 1.4.3 BUG(已修复) 在使用npm引入后使用webpack打包时报错,因为有个文件使用了class且没有constructor()方法初始化属性,直接给一个属性赋了值,这样webpack loder会报错,不晓得为什么。 #### 1.4.2 新加 handleTasks 方法,执行所有的任务,使用深层次的代理对象修改时最后一次是放在下一个微任务(使用的 queueMicrotask Api,如果浏览器不支持则使用setTimeout)中修改,所以 handleTasks 方法是会直接执行添加进的那个宏任务。 #### 1.4.1 对不是深层次的代理对象修改后立即更新到本地储存 #### 1.4 添加类的形式引入 #### 1.3 修复BUG 存储字符串的情况下会多出两个冒号(致命错误) #### 1.2 修复BUG 1:储存的是数组时,取出来重新赋值会丢失map,filter等自带的方法。原因:json化数据时触发了代理的set属性,会调用clone方法,因为当时考虑到数据的储存简单clone一下就行,现在删除了clone。bug解决。 2:直接调用localStorageX()生成对象从而控制键值,以及更深层次的值,我认为这样不好。解决:取消深层代理。
1月前
一篇文字
**效果图** ![一篇文字](https://img10.360buyimg.com/ddimg/jfs/t1/94351/27/17449/22644/61400f55Ecf17081a/aef9fd6ff6e18f61.png) **源码** ```javascript <template> <div class="cp_container tree-container" :style="{ flexDirection:direction=='level'?'row':'column', '--my-gap':gap || '15px', }"> <div v-if="!isStart && parentNode.child && parentNode.child.length>1" :class="{ location_el:true, 'start_location_el-vertical':direction=='vertical', 'start_location_el-level':direction=='level', }"> </div> <div v-if="!isEnd && parentNode.child && parentNode.child.length>1" :class="{ location_el:true, 'end_location_el-vertical':direction=='vertical', 'end_location_el-level':direction=='level', }"> </div> <div class="name tree-name-container" @click="onItem(nodeData,parentNode,zIndex)"> <svg-icon :icon-class="nodeData.icon" v-if="nodeData.icon"></svg-icon> {{nodeData.name}} <div class="bt_list"> <i class="el-icon-plus" @click.stop="onAdd(nodeData,parentNode,zIndex)"> </i> <i class="el-icon-minus" @click.stop="onDelete(nodeData,parentNode,zIndex)"> </i> <i class="el-icon-edit-outline" @click.stop="onUpdate(nodeData,parentNode,zIndex)"> </i> </div> <div v-if="parentNode.name" :class="{ center_el:true, 'start_center_el-vertical':direction=='vertical', 'start_center_el-level':direction=='level', }"> <span></span> </div> <div v-if="nodeData.child && nodeData.child.length>0" :class="{ center_el:true, 'end_center_el-vertical':direction=='vertical', 'end_center_el-level':direction=='level', }"> <span></span> </div> </div> <div class="container tree-childs-container" v-if="nodeData.child && nodeData.child.length>0" :style="{ gridTemplateColumns:direction!='level'?`repeat(${nodeData.child.length},max-content)`:'', gridTemplateRows:direction=='level'?`repeat(${nodeData.child.length},max-content)`:'', paddingTop:direction!='level'?'calc(var(--my-gap) * 2)':'', paddingLeft:direction=='level'?'calc(var(--my-gap) * 2)':'', }"> <MyTree v-for="(item,index) in nodeData.child" :key="index" :zIndex="zIndex+1" :direction="direction" :parentNode="nodeData" :nodeData='item' :isStart='index == 0 && nodeData.child.length>1' :isEnd='index == (nodeData.child.length - 1) && nodeData.child.length>1' @onClick="onItem($event.nodeData,$event.parentNode,$event.zIndex)" @onAdd="onAdd($event.nodeData,$event.parentNode,$event.zIndex)" @onUpdate="onUpdate($event.nodeData,$event.parentNode,$event.zIndex)" @onDelete="onDelete($event.nodeData,$event.parentNode,$event.zIndex)"> </MyTree> </div> </div> </template> <script> //部门树结构组件 提供css让外部修改 export default { name:'MyTree', props:{ zIndex:{ //结构层级 type:Number, default:0, }, direction:{ //结构方向 type:String, default:'vertical', }, nodeData:{ //节点数据对象 type:Object, default:()=>{ return {}; }, }, parentNode:{ //父节点 type:Object, default:()=>{ return {}; }, }, isStart:{ //是左侧 type:Boolean, default:false, }, isEnd:{ //是右侧 type:Boolean, default:false, }, gap:{ //间隔 type:String, default:'15px', }, }, data() { return { }; }, methods:{ onItem(nodeData,parentNode,zIndex){ //点击事件 this.$emit('onClick',{ parentNode, nodeData, zIndex, }); }, onAdd(nodeData,parentNode,zIndex){ this.$emit('onAdd',{ parentNode, nodeData, zIndex, }); }, onUpdate(nodeData,parentNode,zIndex){ this.$emit('onUpdate',{ parentNode, nodeData, zIndex, }); }, onDelete(nodeData,parentNode,zIndex){ this.$emit('onDelete',{ nodeData, parentNode, zIndex, }); }, }, }; </script> <style scoped lang="scss"> .cp_container{ width: fit-content; height: fit-content; display: flex; align-items: center; position: relative; --border-color: #ccc; //边框颜色 >.location_el{ position: absolute; } >.start_location_el-vertical{ height: var(--my-gap); width: calc(50% + var(--my-gap)); left: calc(0px - var(--my-gap)); top: calc(0px - var(--my-gap)); border-top: 1px solid var(--border-color); } >.start_location_el-level{ width: var(--my-gap); height: calc(50% + var(--my-gap)); left: calc(0px - var(--my-gap)); top: calc(0px - var(--my-gap)); border-left: 1px solid var(--border-color); } >.end_location_el-vertical{ height: var(--my-gap); width: calc(50% + var(--my-gap)); right: calc(0px - var(--my-gap)); top: calc(0px - var(--my-gap)); border-top: 1px solid var(--border-color); } >.end_location_el-level{ width: var(--my-gap); height: calc(50% + var(--my-gap)); left: calc(0px - var(--my-gap)); bottom: calc(0px - var(--my-gap)); border-left: 1px solid var(--border-color); } >.name{ max-width: 400px; padding:15px; border-radius: 3px; font-size: 20px; box-sizing: border-box; background-color: rgb(255, 255, 255); cursor: pointer; display: flex; justify-content: center; align-items: center; position: relative; box-shadow: 0 1px 4px #00152936; color: #3e4761; >svg{ margin-right: 5px; min-width: 20px; width: 20px; height: 20px; } &:hover{ .bt_list{ opacity: 1; } } >.bt_list{ opacity: 0; display: grid; position: absolute; height: 30px; padding: 0 5px; border-radius: 2px; background-color: rgb(255, 255, 255); bottom: -35px; border: 1px solid #dadada; box-sizing: border-box; z-index: 999; grid-gap: 5px; grid-template-columns: 1fr 1fr 1fr; align-items: center; transition: all 0.2s; box-shadow: 0 1px 4px #00152936; &:hover{ opacity: 1; } } //center 居中的四个边框 >.center_el{ position: absolute; display: flex; justify-content: center; align-items: center; pointer-events: none; >span{ background-color: var(--border-color); } } >.start_center_el-vertical{ width: 100%; height: var(--my-gap); right: 0; top: calc(0px - var(--my-gap)); >span{ width: 1px; height: 100%; } } >.start_center_el-level{ width: var(--my-gap); height: 100%; left: calc(0px - var(--my-gap)); top: 0; >span{ height: 1px; width: 100%; } } >.end_center_el-vertical{ width: 100%; height: var(--my-gap); right: 0; bottom: calc(0px - var(--my-gap)); >span{ width: 1px; height: 100%; } } >.end_center_el-level{ width: var(--my-gap); height: 100%; right: calc(0px - var(--my-gap)); top: 0; >span{ height: 1px; width: 100%; } } } >.container{ display: grid; grid-gap: var(--my-gap); justify-content: center; width: fit-content; } } </style> ``` 使用方法 ```javascript <MyTree :nodeData="treeData" direction="level" @onAdd="onAdd" @onUpdate="onUpdate" @onDelete="onDelete"> </MyTree> //数据 treeData:{ name:'客户申报', child:[], }, ``` 默认横向排列,非常简单。 其中 `svg-icon`为我自定义的全局组件,不用直接删除就是了。
2月前
#### 直接看代码。可以进行请求拦截,返回数据拦截,并不是用promise进行封装的,因为需要使用到上传进度,所以是简单的回调方式封装的。 ``` /*jshint esversion: 9 */ //回调类型请求工具 //官方文档 https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest class XMLHttp{ constructor({ timeOut=13000000, baseUrl='', }={}){ this.timeOut = timeOut; this.baseUrl = baseUrl; this.intercept = { //拦截器 request:{ success:undefined, error:undefined, }, response:{ success:undefined, error:undefined, }, }; } createRequest({ method,url,params,responseType,async=true,withCredentials=false, readyStateChange, loadStart, progress, abort, error, load, timeOut, loadend, }){ try{ const xhr = new XMLHttpRequest(); xhr.onreadystatechange = ()=>{ readyStateChange && readyStateChange(xhr); }; xhr.upload.addEventListener("progress", (evt)=>{ //上传进度 progress && progress(evt); }); xhr.addEventListener("load", ()=>{ load && load(xhr); }); xhr.addEventListener("error", ()=>{ error && error(xhr); }); xhr.addEventListener("abort", ()=>{ abort && abort(xhr); }); xhr.addEventListener("loadstart", ()=>{ loadStart && loadStart(xhr); }); xhr.addEventListener("loadend", ()=>{ loadend && loadend(xhr); }); if(async){ //异步才添加超时 xhr.timeout = this.timeOut; xhr.addEventListener("timeout", ()=>{ timeOut && timeOut(xhr); }); xhr.responseType = responseType || 'text'; //返回数据类型 } xhr.withCredentials = withCredentials; //指示是否应使用Cookie或授权标头等凭据进行跨站点访问控制请求。 xhr.open( method ? method.toUpperCase() : 'GET', this.baseUrl + url, async, ); this.intercept.request.success && this.intercept.request.success(xhr); //请求拦截 xhr.send(params); return xhr; }catch(e){ this.intercept.request.error && this.intercept.request.error(e); } } setRequestIntercept(success,error){ //写入请求拦截器 this.intercept.request.success = success; this.intercept.request.error = error; } setResponseIntercept(success,error){ //写入返回数据拦截器 this.intercept.response.success = success; this.intercept.response.error = error; } requestMode({ //请求方式 url='',params={},async,withCredentials,method, onSuccess=()=>{}, error=()=>{}, progress=()=>{}, loadend=()=>{}, }={}){ return this.createRequest({ url, params, async, withCredentials, method, progress, loadend, readyStateChange:(xhr)=>{ if(xhr.readyState !== 4) return; if(xhr.status === 200) { //请求成功正确 this.intercept.response.success? //返回数据拦截 this.intercept.response.success( xhr, { onSuccess, error, }, ): onSuccess(xhr); } else { //请求失败 this.intercept.response.error? //返回数据拦截 this.intercept.response.error( xhr, { onSuccess, error, }, ): error(xhr); } }, }); } get(option){ return this.requestMode({ ...option, method:'get', }); } post(option){ return this.requestMode({ ...option, method:'post', }); } put(option){ return this.requestMode({ ...option, method:'put', }); } delete(option){ return this.requestMode({ ...option, method:'delete', }); } } /* test 测试 */ // let a = new XMLHttp(); // a.get({ // async:false, // method:'get', // url:'https://www.w3school.com.cn/js/js_try_catch.asp', // onSuccess(xhr){ // console.log(xhr); // }, // }) // console.log(1); ``` [官方XMLHttpRequest文档](https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest)
3月前
#### 单个文件选择组件 代码 ``` <template> <div class="cp_container"> <div class="bt" @click="onClick"> <template> {{title || '选择文件'}} </template> </div> <input ref="input" type="file" name="file" :accept="accept" @change="changeFile"/> </div> </template> <script> //单个文件选择组件 export default { props:{ title:{ type:String, default:'', }, accept:{ type:String, default:'', }, }, methods: { onClick(){ //选择文件 this.$refs.input.value = ''; //清空选择的文件 this.$refs.input.click(); }, changeFile(e) { this.$emit('onChange', e.target.files[0]); }, }, } </script> <style scoped lang="scss"> .cp_container{ position: relative; .bt { padding: 10px; color: #1e3c6a; background-color: #1dc979; border-radius: 3px; font-size: 15px; text-align: center; font-weight: bold; box-sizing: border-box; cursor: pointer; } input{ position: absolute; display:none; top: 0; left: 0; } } </style> ``` 单个图片文件选择 ``` <template> <div class="cp_container"> <div class="bt" @click="onClick"> <img v-if="imgUrl" :src="imgUrl" alt=""> <span v-else> + </span> </div> <input ref="input" type="file" name="file" :accept="accept || 'image/*'" @change="changeFile"/> </div> </template> <script> //单个图片文件选择组件(只做选择) export default { props:{ imgUrl:{ //图片地址 type:String, default:'', }, accept:{ //文件类型限制 type:String, default:'', }, }, methods: { onClick(){ //选择文件 this.$refs.input.value = ''; //清空选择的文件 this.$refs.input.click(); }, changeFile(e) { this.$emit('onChange', e.target.files[0]); }, }, } </script> <style scoped lang="scss"> .cp_container{ position: relative; .bt { color: #1e3c6a; background-color: #dfdfdf; border-radius: 3px; text-align: center; font-weight: bold; box-sizing: border-box; cursor: pointer; width: 100px; height: 100px; display: flex; justify-content: center; align-items: center; border: 1px solid #b3b3b3; >img{ width: 100%; height: 100%; border-radius: 3px; object-fit: cover; } >span{ font-size: 30px; line-height: 1; } } input{ position: absolute; display:none; top: 0; left: 0; } } </style> ```
3月前
毒蘑菇搜索
![毒蘑菇搜索](https://img11.360buyimg.com/ddimg/jfs/t1/188328/17/16806/2521677/610b881aEbd7b3090/df91ca3d3b9e9f53.png) #### 因为自己的小站首页需要一个可以滑动切换的显示内容的功能,本来是用的swiper,但是坑,BUG实在是太多了,实在是搞不明白了,就这么个小功能还是决定自己写一个组件。 直接贴代码,基本都含有注释 ```javascript <template> <div class="touch_tab_container" ref="TabContainerEl" :style="{ height:height+'px', }" @touchmove="(e)=>{ mouseMove(e.touches[0]); }" @touchstart="(e)=>{ onMouseDown(e.touches[0]); }" @touchend="(e)=>{ onMouseUp(e.touches[0]); }" @mousemove="mouseMove" @mouseleave="onMouseLeave" @mousedown="onMouseDown" @mouseup="onMouseUp"> <div :class="{ container:true, animation:animation, }" ref="ContainerEl" :style="{ transform:`translateX(${translateX}px)`, }"> <slot></slot> </div> </div> </template> <script> //自定义touchTab组件 export default { name:'TouchTab', data() { return { height:0, //总容器高度 TabContainerEl:undefined, //总容器元素 ContainerEl:undefined, tabItemEls:[], mouseDown:false, //鼠标是否按下 translateX:0, //容器的偏移量 animation:false, //是否开启容器动画 activeIndex:0, speed:0, //移动速度 }; }, computed:{ translateX_:{ //translateX的代理层 set(newValue){ if(!this.ContainerEl) return; if(newValue > 0) return; if(newValue < this.TabContainerEl.clientWidth-this.ContainerEl.clientWidth) return; this.translateX = newValue; if(this.mouseDown){ //如果鼠标按下的话计算移动速率 let oldTranslateX = this.oldTranslateX || newValue; let oldDate = this.oldDate || new Date().getTime(); let newTranslateX = newValue; let newDate =new Date().getTime(); this.speed = Math.abs((newTranslateX - oldTranslateX)/(newDate - oldDate)); this.oldTranslateX = newTranslateX; this.oldDate = newDate; } }, get(){ return this.translateX; }, }, }, watch:{ activeIndex:{ //当前选项发生变化时向外部暴露当前显示的选项索引 handler(newVlaue){ this.$emit('onChange',newVlaue); }, immediate:true, }, }, mounted(){ this.TabContainerEl = this.$refs.TabContainerEl; this.ContainerEl = this.$refs.ContainerEl; this.tabItemEls = this.$refs.ContainerEl.children; this.setActiveEl(this.activeIndex); //设置初始显示item const that = this; requestAnimationFrame(function func(){ //每一帧都计算样式 that.computeStyle(); requestAnimationFrame(func); }); }, methods:{ mouseMove(e){ //鼠标移动事件 if(!this.mouseDown) return; //必须是鼠标按下 this.animation = false; let clientX = e.clientX - this.lastEnven.clientX; this.translateX_ = this.translateX_ + clientX; this.lastEnven = e; }, onMouseLeave(){ //鼠标移出事件 this.mouseDown = false; this.lastEnven = undefined; this.computeIndex(); }, onMouseDown(e){ //鼠标按下事件 this.mouseDown = true; this.lastEnven = e; }, onMouseUp(){ //鼠标抬起事件 this.mouseDown = false; this.lastEnven = undefined; this.computeIndex(); }, computeStyle(){ //计算样式 if(this.tabItemEls.length>0){ this.tabItemEls.forEach(el => { //设置子元素的宽度与总容器宽度一致 el.style.width = this.TabContainerEl.clientWidth+'px'; el.style.minWidth = this.TabContainerEl.clientWidth+'px'; }); this.height = this.tabItemEls[this.activeIndex].clientHeight; } }, computeIndex(){ //计算索引位置 let itemWidth = this.TabContainerEl.clientWidth; let translateX = Math.abs(this.translateX_); let width_ = itemWidth*this.activeIndex - translateX; let direction = width_>0?'left':'right'; width_ = Math.abs(width_); if(width_>(itemWidth/2)){ //如果滑动超过了一半 this.setActiveEl( direction=='right'?this.activeIndex+1:this.activeIndex-1, ); }else if(this.speed>0.5){ //表示没有超过一半,可以通过滑动速率和方向判断 this.setActiveEl( direction=='right'?this.activeIndex+1:this.activeIndex-1, ); }else{ this.setActiveEl(this.activeIndex); } this.speed = 0; }, setActiveEl(index){ //设置当前显示的item(可外部调用) this.animation = true; if(index<=0){ this.activeIndex = 0; }else if(index>(this.tabItemEls.length-1)){ this.activeIndex = this.tabItemEls.length-1; }else{ this.activeIndex = index; } this.translateX_ = -this.TabContainerEl.clientWidth * this.activeIndex; }, }, } </script> <style lang="scss" scoped> .touch_tab_container{ width: 100%; margin: 0 auto; background-color: aquamarine; border-radius: 5px; height: fit-content; overflow: hidden; transition: height 0.2s; >.container{ -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; width:fit-content; display: flex; transform: translateX(0px); >.item{ min-width: 0; width: 0; height: fit-content; } } .animation{ transition: all 0.2s; transition-timing-function: cubic-bezier(0.69, 0.05, 0.24, 1.01); } } </style> ``` 使用方法 ```javascript <myComponent ref="myComponent" @onChange="onChange"> </myComponent> //直接设置显示那个选项 this.$refs.myComponent.setActiveEl(1); ``` [示例,首页这里的滑动翻页就是这种形式](https://search.dumogu.top/)
3月前
下一页
最近