一.前言


文章主要以宏观的形式来聊哔哩哔哩侧边导航拖拽组件,非常适合正在渐进式学习VUE的你,适当的模仿开发项目是前端学习必须要有的技能。大多数人都知道的是,面试需要有自己的作品,而作品最重要的不是切页面,而是:创新+用户体验+性能优化+技术展示 。作者也是一个前端小白,正在摸索阶段,我今天讲解的是模仿我觉得做的不错的侧边导航栏,希望大家有收获。让我们一起来,淡黄的长裙,蓬松的头发,拽拽拽!

组件展示

这是一个模仿老版哔哩哔哩的侧边导航栏组件,部分效果如下图:

根据效果图可以看出,组件拥有以下功能:

  1. 导航栏中的条目元素item可以进行拖拽,并且页面专题结构同步改变。
  2. 点击任意条目元素item,可以立即到其对应的页面位置。
  3. 当浏览页面时,移动的某个专题时,旁边的条目元素item也会与之对应。

二.具体讲解

  • 根据需求:本文将简述对h5和css进行编写,重点是如何实现实时滚动导航和拖拽。

获取专题名称及其相关数据

1.首先我们要去vuex里面拿数据,完成显示专题名称,拖拽等功能,需要sortValuessortKeys以及sortIds,vuex通过去请求哔哩哔哩官方提供的api进行拿取。具体过程暂且忽略,部分代码如下(因为这个是一个全栈项目,而这个组件和其他组件的关联程度最大,所以作者有点不好如何讲解,还望多多谅解,文末将会附上guthub地址):

import { contentApi, contentrankApi } from '@/api'
import * as TYPE from '../actionType/contentType' //采用actionType便于开发与管理const state = {// 默认排序sortKeys: ['douga', 'bangumi', 'music', 'dance', 'game', 'technology', 'life', 'kichiku', 'fashion', 'ad', 'ent', 'movie', 'teleplay'],sortIds: [1, 13, 3, 129, 4, 36, 160, 119, 155, 165, 5, 23, 11],sortValues: ['动画', '番剧', '音乐', '舞蹈', '游戏', '科技', '生活', '鬼畜', '时尚', '广告', '娱乐', '电影', 'TV剧'],rows: [],ranks: [],rank: {}
}const getters = {rows: state => state.rows,sortKeys: state => state.sortKeys,sortIds: state => state.sortIds,ranks: state => state.ranks,rank: state => state.rank,sortValues: state => state.sortValues
}const actions = {getContentRows({commit, state, rootState}) {rootState.requesting = truecommit(TYPE.CONTENT_REQUEST)contentApi.content().then((response) => {rootState.requesting = falsecommit(TYPE.CONTENT_SUCCESS, response)}, (error) => {rootState.requesting = falsecommit(TYPE.CONTENT_FAILURE)})},getContentRank({commit, state, rootState}, categoryId) {console.log(categoryId)rootState.requesting = truecommit(TYPE.CONTENT_RANK_REQUEST)let param = {categoryId: categoryId}contentrankApi.contentrank(param).then((response) => {rootState.requesting = falseif (categoryId === 1) {console.log(response)}commit(TYPE.CONTENT_RANK_SUCCESS, response)}, (error) => {rootState.requesting = falsecommit(TYPE.CONTENT_RANK_FAILURE)})}
}
const mutations = {[TYPE.CONTENT_REQUEST] (state) {},[TYPE.CONTENT_SUCCESS] (state, response) {for (let i = 0; i < state.sortKeys.length; i++) {let category = state.sortKeys[i] let rowItem = {category: category,categoryId: state.sortIds[i],name: state.sortValues[i],b_id: `b_${category}`,item: Object.values(response[category])}state.rows.push(rowItem)}},[TYPE.CONTENT_FAILURE] (state) {},// 排行榜信息[TYPE.CONTENT_RANK_REQUEST] (state) {},[TYPE.CONTENT_RANK_SUCCESS] (state, response) {state.ranks.push(response)state.rank = response},[TYPE.CONTENT_RANK_FAILURE] (state) {}
}export default {state,getters,actions,mutations
}

2. 接下来,我们要做的事情就是就是对数据进行初始化。作者先上代码再来解释,代码如下:

import { mapGetters } from "vuex";
export default {mixins: [scrollMixin],data() {return {current: 0, //当前选中条目的序号data: [], //数据(name,element,offsetTop,height)time: 800, //动画时间height: 32, //单个元素的高度isSort: false, //排序模式scrollTop: 0, //距离页面的顶部距离dragId: 0, //拖拽元素序号isDrag: false, //当前是否在拖拽offsetX: 0, //鼠标在要拖拽的元素上的X坐标上的偏移offsetY: 0, //鼠标在要拖拽的元素上的Y坐标上的偏移x: 0, //被拖拽的元素在其相对的元素上的X坐标上的偏移y: 0 //被拖拽的元素在其相对的元素上的Y坐标上的偏移};},

首先我们将所有我们实现需求所需的数据,全部简单初始化写在data,如我们需要实现页面滚动时条目跟随专题,就需要获取这个条目的序号,名字,元素以及距离页面顶部的高度等等。要实现可以把条目进行拖拽,就需要获取是否参与拖拽状态,正在拖拽哪一个条目,所有需要获取拖拽的条目序号以及鼠标的一些数据。

仅仅向上面这样初始化数据是远远不够的,要实现需求就必须在兼容所有浏览器的情况下,获取整个网页的大小宽高数据以及对鼠标的操作有着实时的监听。作者先上代码:

methods: {/** 初始化 */init() {this.initData(); //初始化this.bindEvent();this._screenHeight = window.screen.availHeight; //返回当前屏幕高度(空白空间) this._left = this.$refs.list.getBoundingClientRect().left;//方法返回元素的大小及其相对于视口的位置。this._top = this.$refs.list.getBoundingClientRect().top;},/** 绑定事件 */bindEvent() {document.addEventListener("scroll", this.scroll, false);document.addEventListener("mousemove", this.dragMove, false);//当指针设备( 通常指鼠标 )在元素上移动时, mousemove 事件被触发。document.addEventListener("mouseup", this.dragEnd, false);//事件在指针设备按钮抬起时触发。document.addEventListener("mouseleave", this.dragEnd, false);//指点设备(通常是鼠标)的指针移出某个元素时,会触发mouseleave事件。//mouseleave  和 mouseout 是相似的,但是两者的不同在于mouseleave 不会冒泡而mouseout 会冒泡。//这意味着当指针离开元素及其所有后代时,会触发mouseleave,而当指针离开元素或离开元素的后代(即使指针仍在元素内)时,会触发mouseout。},/** 初始化data */initData() {//将this.options.items转化成新的数组this.datathis.data = Array.from(this.options.items, item => {let element = document.getElementById(item.b_id);if (!element) {console.error(`can not find element of name is ${item.b_id}`);return;}let offsetTop = this.getOffsetTop(element);return {name: item.name,element: element,offsetTop: offsetTop,//返回当前元素相对于其 offsetParent 元素的顶部的距离。height: element.offsetHeight//它返回该元素的像素高度,高度包含该元素的垂直内边距和边框,且是一个整数。};});},//获取元素距离顶部的距离getOffsetTop(element) {let top,clientTop,clientLeft,scrollTop,scrollLeft,doc = document.documentElement,//返回元素body = document.body;if (typeof element.getBoundingClientRect !== "undefined") {top = element.getBoundingClientRect().top;} else {top = 0;}clientTop = doc.clientTop || body.clientTop || 0;//表示一个元素的上边框的宽度.boderscrollTop = window.pageYOffset || doc.scrollTop;//返回当前页面相对于窗口显示区左上角的 Y 位置。浏览器兼容return top + scrollTop - clientTop;},}
  • init():在浏览器中打开可能是全屏或者是小窗,此时页面的大小高度都会改变,我们必须每次当浏览器窗口大小变化时,重新获取(初始化),当前屏幕的高度以及每个条目元素相对窗口的位置,只有这样才可以在不同的情况下,也不出错,实时变化。使用screen.availHeight.availHeight获取屏幕高度,使用getBoundingClientRect()方法来获取条目元素相对于视窗的位置,如下图所示。

  • bindEvent():这个方法里面写了对鼠标操作以及滚动的行为进行事件绑定,也可说监听,这是实现实时变化的关键。这个方法里面我要特别说一下的是我们使用mouseleave,而不使用mouseout,的原因是我们需要实现进行拖拽时,当条目元素脱出侧边栏,这个元素将不会显示了(下面将放上展示动图),因为触发了mouseleave,这个方法是当鼠标离开其父组件时触发。不使用mouseout是因为这个方法离开元素自己的位置就会触发离开其父级元素的时候也会触发,是冒泡触发的。这里我们使用一定要准确,如果你还是有点不理解,可以去试试MDN上的对比演示demo演示demo文档。

  • initData(): 将this.options.items转化成新的数组this.data,返回名字、元素本身、元素相对于其 offsetParent 元素的顶部的距离以及该元素的像素高度,高度包含该元素的垂直内边距和边框。

  • getOffsetTop():获取条目元素距离顶部的距离,这里作者不过多讲解推荐一篇文章JavaScript之scrollTop、scrollHeight、offsetTop、offsetHeight等属性学习笔记。需要讲解的是return top + scrollTop - clientTop;元素本身的高度加上滚动增加的高度减去一个重复的上边框高度才是实际的元素的高度

3. 现在我们就要开始实现第一个功能,点击条目元素,网页移动到对应的位置,我们要实现这个功能很容易,只要获取对应条目元素的位置和index就可以实现,但是要实现平滑的滚动需要引入smooth-scroll.js代码如下:

        <divclass="n-i sotrable":class="[{'on': current===index && !isSort}, {'drag': isDrag && current === index}]"@click="setEnable(index)"@mousedown="dragStart($event, index)":style="dragStyles":key="index"><div class="name">{{item.name}}</div></div><div class="btn_gotop" @click="scrollToTop(time)"></div>setEnable(index) {if (index === this.current) {return false;}this.current = index;let target = this.data[index].element;this.scrollToElem(target, this.time, this.offset || 0).then(() => {});},

smooth-scroll.js

window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrameconst Quad_easeIn = (t, b, c, d) => c * ((t = t / d - 1) * t * t + 1) + bconst scrollTo = (end, time = 800) => {let scrollTop = window.pageYOffset || document.documentElement.scrollToplet b = scrollToplet c = end - blet d = timelet start = nullreturn new Promise((resolve, reject) => {function step(timeStamp) {if (start === null) start = timeStamplet progress = timeStamp - startif (progress < time) {let st = Quad_easeIn(progress, b, c, d)document.body.scrollTop = stdocument.documentElement.scrollTop = stwindow.requestAnimationFrame(step)}else {document.body.scrollTop = enddocument.documentElement.scrollTop = endresolve(end)}}window.requestAnimationFrame(step)})
}const scrollToTop = (time) => {time = typeof time === 'number' ? time : 800return scrollTo(0, time)
}const scrollToElem = (elem, time, offset) => {let top = elem.getBoundingClientRect().top  + ( window.pageYOffset || document.documentElement.scrollTop )  - ( document.documentElement.clientTop || 0 )return scrollTo(top - (offset || 0), time)
}export default {methods: {scrollToTop,scrollToElem,scrollTo}
}

关于smooth-scroll.js,作者推荐自己查一下资料,有比较多。

4. 实现页面滚动时条目元素跟随对应,代码如下:

     //  偏移值offset() {return this.options.offset || 100;},/** 滚动事件 */scroll(e) {this.scrollTop =window.pageYOffset ||document.documentElement.scrollTop + document.body.scrollTop;//浏览器兼容,返回当前页面相对于窗口显示区左上角的 Y 位置if (this.scrollTop >= 300) {this.$refs.navSide.style.top = "0px";this.init();} else {this.$refs.navSide.style.top = "240px";this.init();}// console.log("距离顶部" + this.scrollTop);//实时跟踪页面滚动for (let i = 0; i < this.data.length; i++) {if (this.scrollTop >= this.data[i].offsetTop - this.offset) {this.current = i;}}},

这里我们可以看到,我们使用了初始化里面的数据,然后滚动的关键就是获得元素到窗口的距离以及偏移值。需要注意的一个细节是滚动时元素与窗口顶部的距离大于300px时,整个组件将吸顶。

5. 实现拖拽

  1. 进入排序模式
  <div class="nav-side" :class="{customizing: isSort}" ref="navSide">  <!--默认不进行排序--><transition name="fade"><div v-if="isSort"><div class="tip"></div><div class="custom-bg"></div></div></transition></div>//进入排序模式sort() {this.isSort = !this.isSort;this.$emit("change");},.fade-enter-actice, .fade-leave-active {transition: opacity 0.3s;}.fade-enter, .fade-leave-active {.tip {top: 50px;opacity: 0;}.custom-bg {top: 150px;left: -70px;height: 100px;width: 100px;opacity: 0;}}
}

通过上面的代码可知,进入排序模式的代码比较简单,主要是由css的动画来实现。

2.开始拖拽

/** 得到鼠标位置 */getPos(e) {this.x = e.clientX - this._left - this.offsetX;this.y = e.clientY - this._top - this.offsetY;},
/** 拖拽开始 */dragStart(e, i) {if (!this.isSort) return false;this.current = i;this.isDrag = true;this.dragId = i;this.offsetX = e.offsetX;this.offsetY = e.offsetY;this.getPos(e);},

开始拖拽时,需要判断是否进入了排序,进入了才允许可以进行拖拽,此时获得鼠标选中的位置,元素的位置以及对应id。

3.拖拽中

<template v-for="(item, index) in data" ><divv-if="isDrag && index === replaceItem && replaceItem <= dragId"class="n-i sotrable":key="item.name"><div class="name"></div></div><divclass="n-i sotrable":class="[{'on': current===index && !isSort}, {'drag': isDrag && current === index}]"@click="setEnable(index)"@mousedown="dragStart($event, index)":style="dragStyles":key="index"><div class="name">{{item.name}}</div></div><divv-if="isDrag && index === replaceItem && replaceItem > dragId"class="n-i sotrable":key="item.name"><div class="name"></div></div>
</template>// 拖拽的元素的position会变为absolute,dragStyles用来设置其位置,鼠标运动时会调用,从而实现跟随鼠标运动dragStyles() {return {left: `${this.x}px`,top: `${this.y}px`};},//当被拖拽的元素运动到其他元素的位置时,会使得replaceItem发送变化replaceItem() {let id = Math.floor(this.y / this.height);if (id > this.data.length - 1) id = this.data.length;if (id < 0) id = 0;return id;}/** 拖拽中 */dragMove(e) {if (this.isDrag) {this.getPos(e);}e.preventDefault();//该方法将通知 Web 浏览器不要执行与事件关联的默认动作(如果存在这样的动作)},

进入拖拽时,首要的是判断是否获取了要拖拽元素的鼠标位置,如果没有获取到,将无法进行拖拽,则使用e.preventDefault()通知浏览器不进行拖拽。然后使用dragStyles()获取元素拖拽的实时位置。最后元素拖拽时会改变其他元素的位置,位置改变了,其对应的id就会发生变化,我们通过replaceItem()来实现,在这个方法里面,我们奇妙的利用元素的实时高度与元素本身的高度相除获得动态的id

  1. 拖拽完成
    /** 拖拽结束 */dragEnd(e) {if (this.isDrag) {this.isDrag = false;if (this.replaceItem !== this.dragId) {this.options.items.splice(this.replaceItem,0,this.options.items.splice(this.dragId, 1)[0]);} else {this.setEnable(this.dragId, true);}

这段代码巧妙的是,首先判断是否还在进行拖拽如果有,则this.isDrag = false;停止拖拽,接着就是核心部分巧妙利用splice,如果this.replaceItem !== this.dragId,则在this.replaceItem后面添加this.options.items.splice(this.dragId, 1)[0],即这个拖拽元素初始id,相当于拖拽不成功,回到原来的位置,否则拖拽成功。下面我用动图来演示一下。


最后今天是清明节,也是我们深切悼念新冠肺炎疫情牺牲的烈士和逝世同胞的日子,把网站变灰。

在全局中加上如下css就好,代码如下,参考文章tuitui

  #app filter grayscale(100%)-webkit-filter grayscale(100%)-moz-filter grayscale(100%)-ms-filter grayscale(100%)-o-filter grayscale(100%)filter url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'grayscale\'><feColorMatrix type=\'matrix\' values=\'0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0\'/></filter></svg>#grayscale")filter progid:DXImageTransform.Microsoft.BasicImage(grayscale=1)-webkit-filter: grayscale(1)

效果图:


结束

文章看到现在也结束啦,如果有错误的话就麻烦大家给我指出来吧!如果觉得不错的话别忘了点个赞

Drag,drag,drag!拽出哔哩哔哩侧边导航组件相关推荐

  1. Python3 爬虫实战 — 模拟登陆哔哩哔哩【滑动验证码对抗】

    登陆时间:2019-10-21 实现难度:★★★☆☆☆ 请求链接:https://passport.bilibili.com/login 实现目标:模拟登陆哔哩哔哩,攻克滑动验证码 涉及知识:滑动验证 ...

  2. 哔哩哔哩验证码的破解

    ''' 极验验证码 1.抠图片1)带有缺口的图片2)不带缺口的图片 2.比较两张图片的像素,获取移动距离 3.拖拽 ''' import time from selenium import webdr ...

  3. python爬取哔哩哔哩视频_荐爬取哔哩哔哩中的cosplay小视频

    爬取哔哩哔哩小视频 前言:想必大家都对小视频感兴趣吧,今天的爬虫的内容为将哔哩哔哩中的视频下载到本地,今天爬取的网站为 URL : https://vc.bilibili.com/p/eden/all ...

  4. 哔哩哔哩注册--表单练习

    哔哩哔哩注册–表单练习 HTML代码 可能代码有些不足或者用词不当等 希望大家能够见谅 这是一次bilibili的注册表单练习 里面都有详细注释 当然一个网页有许多种 这只是其中一种 是为了相互交流 ...

  5. 哔哩哔哩_哔哩哔哩,危!!!

    如何下载哔哩哔哩上的视频?以前的时候,唧唧Down很好用,不管是网页还是客户端,都能满足要求.最近在用的时候,网页端老是出问题:用客户端吧,也感觉没有以前顺手了.今天就给大家介绍一个同类型下载器,到底 ...

  6. Python模拟登录哔哩哔哩

    嘿,各位小伙伴中午好呀,今天要带来点什么干货呢,就从我的实际开发中来给大家带来一个案例吧,如何自动登录哔哩哔哩. 接到老大通知,让我自动写一个自动登录哔哩哔哩的脚本,我当然是二话不说直接开怼,咱们的准 ...

  7. B站陈睿:70 后也正在爱上哔哩哔哩

    5月28日,第七届中国网络视听大会在成都正式召开.哔哩哔哩董事长兼CEO陈睿出席并发表题为<守正创新,激发内容新活力>的主题演讲,他表示在过去的三年当中,更广大年龄层和区域的用户正在爱上哔 ...

  8. ant-design 本地web版本下载_bilibili 哔哩哔哩视频如何下载到电脑的 3 种方法

    bilibili(哔哩哔哩)是目前国内知名的视频弹幕网站,被粉丝们亲切的称为"B站". 现为国内领先的年轻人文化社区,非常受年轻用户的欢迎. 其操作界面简洁.速度快而且广告也不多, ...

  9. 2020哔哩哔哩校招后端开发笔试编程题总结

    2020哔哩哔哩校招后端开发笔试编程题总结 1.给定一个正整数N,试求有多少组连续正整数满足所有数字之和为N? (1 <= N <= 10 ^ 9) 暴力求解法: package Day4 ...

最新文章

  1. jmeter名词解释之聚合报告
  2. Ubuntu16.04如何换pip源
  3. Window 7 下的某些服务不能随便禁用! 无法立即删除.exe文件,因为禁用了Application Experience服务。...
  4. 1_3 SingletonMode 单例模式
  5. a jquery 标签点击不跳转_form标签的action属性怎么用?form标签action属性的用法介绍(附实例)...
  6. vba 指定列后插入列_在不同的列左侧插入指定数量的空白列
  7. swagger2使用步骤
  8. leaflet 鼠标移动到图层时变_leaflet 图层切换报错
  9. win10安装影子系统导致的蓝屏
  10. 吃鸡显示连接服务器超时,吃鸡 怎么显示连接超时 | 手游网游页游攻略大全
  11. 射频电路PCB设计技巧
  12. 【Office】编辑宏报错:不能在隐藏工作簿中编辑宏。请选定取消窗口隐藏”命令以显示工作簿
  13. Let's go to the EX
  14. 小程序 - 微信授权登录 微信授权绑定手机号
  15. 表达式和语句的简单理解
  16. 心电matlab,基于matlab检测心电信号
  17. [读书笔记]《一本书读懂财报》
  18. arima模型 白噪声检验_白噪声模型
  19. 什么是雅可比矩阵?利用雅可比矩阵分析动力学
  20. 收集的SQL Server性能相关资料

热门文章

  1. 【CSDN|每日一练】严查枪火
  2. 最佳解决浏览器中文不兼容或中文乱码转UTF-8的方案
  3. 宏基Acer笔记本预装win8系统换成win7系统安装教程
  4. 暑假遥感图像处理+深度学习学习笔记
  5. 人生苦短、我爱python_人生苦短,我爱Python007——基本语法(5)
  6. 值得推荐收藏的 9个免费PDF转PPT的方法
  7. 为什么linux视频关闭这么卡,在Linux使用电视(视频)卡
  8. android:layout_marginleft的作用,LinearLayout中margin属性小结
  9. C# WPF中控件的Margin属性
  10. setCapture和releaseCapture的小应用(转)