Введение
В этой статье в основном рассказывается о компонентах боковой навигации и перетаскивания Bilibili в форме макроса, которая очень подходит для тех, кто изучает VUE постепенно. Соответствующие проекты по имитации разработки являются необходимыми навыками для обучения интерфейсу. То, что знает большинство людей, это то, что у интервью должна быть своя работа, и самое главное для работы — это не вырезать страницу, а: инновации + пользовательский опыт + оптимизация производительности + отображение технологий. Автор также новичок во фронтенде и находится в стадии нащупывания.То, что я объясняю сегодня, является имитацией.Я думаю, что это хорошая работа."боковая панель навигации", я надеюсь, что у каждого есть что-то получить. Пойдем вместе, светло-желтое платье, пышные волосы, туги-туги!
Отображение компонента
❝Это компонент боковой панели навигации, имитирующий старую версию Bilibili. Некоторые из эффектов:
❞
Как видно из рендеров, компонент имеет следующие функции:
- Элемент ввода на панели навигации
itemВы можете перетаскивать, и структура темы страницы меняется синхронно. - Нажмите на любой элемент записи
item, вы можете сразу перейти к соответствующей позиции на странице. - При просмотре страницы переместите тему рядом с элементом ввода
itemтакже будет ему соответствовать.
2. Подробное объяснение
- Согласно требованиям: В этой статье будет кратко описано написание h5 и css с упором на то, как реализовать навигацию с прокруткой и перетаскиванием в реальном времени.
Получить название темы и связанные с ней данные
1. Прежде всего, нам нужно перейти к vuex, чтобы получить данные и выполнить такие функции, как отображение названия темы, перетаскивание и т. д.sortValues,sortKeysтак же какsortIds, vuex получает его, запрашивая API, официально предоставленный Bilibili. Конкретный процесс пока игнорируется, и некоторые из кодов выглядят следующим образом (потому что это проект с полным стеком, и этот компонент наиболее тесно связан с другими компонентами, поэтому автору немного сложно объяснить, и Надеюсь на понимание, а адрес гутхаба будет прикреплен в конце статьи):
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 = true
commit(TYPE.CONTENT_REQUEST)
contentApi.content().then((response) => {
rootState.requesting = false
commit(TYPE.CONTENT_SUCCESS, response)
}, (error) => {
rootState.requesting = false
commit(TYPE.CONTENT_FAILURE)
})
},
getContentRank({commit, state, rootState}, categoryId) {
console.log(categoryId)
rootState.requesting = true
commit(TYPE.CONTENT_RANK_REQUEST)
let param = {
categoryId: categoryId
}
contentrankApi.contentrank(param).then((response) => {
rootState.requesting = false
if (categoryId === 1) {
console.log(response)
}
commit(TYPE.CONTENT_RANK_SUCCESS, response)
}, (error) => {
rootState.requesting = false
commit(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.data
this.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;//表示一个元素的上边框的宽度.boder
scrollTop = window.pageYOffset || doc.scrollTop;//返回当前页面相对于窗口显示区左上角的 Y 位置。浏览器兼容
return top + scrollTop - clientTop;
},
}
- init(): Открыть в браузере может быть полный экран или маленькое окно. В это время размер и высота страницы будут меняться. Мы должны повторно получить (инициализировать) высоту текущего экрана и каждый раз изменяется размер окна браузера Положение каждого элемента ввода относительно окна может быть изменено только в режиме реального времени при различных обстоятельствах без ошибок. использовать
screen.availHeight.availHeightЧтобы получить высоту экрана, используйтеgetBoundingClientRect()чтобы получить положение элемента элемента относительно области просмотра, как показано на следующем рисунке.
- bindEvent(): этот метод записывает привязку событий для операций мыши и поведения прокрутки, что также можно назвать отслеживанием, что является ключом к реализации изменений в реальном времени. Что я хочу сказать в частности об этом методе, так это то, что мы используем
mouseleave, Вместо того, чтобы использоватьmouseout, Причина в том, что когда нам нужно реализовать перетаскивание, когда элемент ввода находится вне сайдбара, этот элемент отображаться не будет (ниже будет отображаться анимация), потому что триггерmouseleave, этот метод запускается, когда мышь покидает родительский компонент. Не используйтеmouseoutЭто связано с тем, что этот метод покидает собственную позицию элемента и срабатывает, когда он покидает свой родительский элемент, который запускается путем всплытия. Здесь мы должны использовать его точно.Если вы все еще не понимаете, вы можете попробовать сравнительную демонстрацию на MDN.Демонстрационная документация.
-
initData(): будет
this.options.itemsПреобразовать в новый массивthis.data, возвращает имя, сам элемент, расстояние элемента от верхнего края его элемента offsetParent и высоту элемента в пикселях, включая вертикальные отступы и границу элемента. -
getOffsetTop(): Получить расстояние элемента входа от вершины, здесь автор не слишком много объясняет, чтобы рекомендовать статьюScrollTop, scrollHeight, offsetTop, offsetHeight и другие атрибуты JavaScript в учебных заметках. То, что нужно объяснить, это
return top + scrollTop - clientTop;"Высота самого элемента плюс высота, добавленная прокруткой, минус высота повторяющейся верхней границы — это фактическая высота элемента.".
3. Теперь мы начнем реализовывать первую функцию, нажмите на элемент записи, и веб-страница переместится в соответствующую позицию.Нам очень легко реализовать эту функцию, если мы получим позицию и соответствующую запись элемент.indexДостичь можно, но для плавной прокрутки нужно ввестиsmooth-scroll.jsкод показывает, как показано ниже:
<div
class="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.msRequestAnimationFrame
const Quad_easeIn = (t, b, c, d) => c * ((t = t / d - 1) * t * t + 1) + b
const scrollTo = (end, time = 800) => {
let scrollTop = window.pageYOffset || document.documentElement.scrollTop
let b = scrollTop
let c = end - b
let d = time
let start = null
return new Promise((resolve, reject) => {
function step(timeStamp) {
if (start === null) start = timeStamp
let progress = timeStamp - start
if (progress < time) {
let st = Quad_easeIn(progress, b, c, d)
document.body.scrollTop = st
document.documentElement.scrollTop = st
window.requestAnimationFrame(step)
}
else {
document.body.scrollTop = end
document.documentElement.scrollTop = end
resolve(end)
}
}
window.requestAnimationFrame(step)
})
}
const scrollToTop = (time) => {
time = typeof time === 'number' ? time : 800
return 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;
}
}
},
Здесь мы видим, что мы используем данные при инициализации, и ключом к прокрутке является получение значения расстояния и смещения элемента до окна. Одна деталь, которую следует отметить, заключается в том, что"Элемент больше 300 пикселей от верхней части окна при прокрутке", вся сборка будет монтироваться на потолке.
5. Реализуйте перетаскивание
❝❞
- войти в режим сортировки
<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);
},
Когда вы начинаете перетаскивать, вам нужно судить, вошли ли вы в сортировку, а затем вы можете перетаскивать, В это время вы можете получить позицию, выбранную мышью, позицию элемента и соответствующий идентификатор.
❝3. перетаскивание
❞
<template v-for="(item, index) in data" >
<div
v-if="isDrag && index === replaceItem && replaceItem <= dragId"
class="n-i sotrable"
:key="item.name"
>
<div class="name"></div>
</div>
<div
class="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
v-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()Получите положение перетаскиваемого элемента в реальном времени. Наконец, когда элемент перетаскивается, положение других элементов будет изменено. Если положение изменится, соответствующий идентификатор изменится. Мы передаемreplaceItem()Для достижения в этом методе мы прекрасно используем"Высота элемента в реальном времени делится на высоту самого элемента для получения динамического идентификатора.".
❝❞
- перетащите готово
/** 拖拽结束 */
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)
Изображение эффекта:
конец
❝На этом статья окончена, если есть ошибки, прошу указать мне на них! Если вы считаете, что это хорошо, не забудьте поставить лайк👍 и вперед! Надеюсь вы -P кто читает статью, давайте, настойчивость страшнее тяжелого труда, пойдем на край света с мечами.
❞
Наконец, прикрепите адрес Github
- Адрес источника:bilibili
Адрес личного блога
- адрес блога:Блог Сяо Сяояна
ожидать
- Весной автор ищет стажировку, с нетерпением ждет благосклонности начальства~