визуализация
иллюстрировать
Эта статья может быть немного затянутой...
Компонентные трудности
- Как прослушать событие завершения прокрутки
- Как слушать события перетаскивания на мобильном телефоне
Предварительные условия
Чтобы дать полную игруvueхарактеристики, мы не должны проходитьrefработать напрямуюdom, но следует изменить элемент данных так, чтобыvueавтоматическое обновлениеdom. Поэтому мы пишемtemplate.
<template>
<div class="ys-float-btn" :style="{'left':left+'px','top':top+'px'}">
<slot name="icon"></slot>
<p>{{text}}</p>
</div>
</template>
Конечно.ys-float-btnОпределенно естьposition:fixedДа, другие стили очень просты, и каждый может играть бесплатно.
Инициализировать местоположение
При первом входе на страницу кнопка должна находиться в исходном положении. Мы инициализируем в созданном хуке.
created(){
this.left = document.documentElement.clientWidth - 50;
this.top = document.documentElement.clientHeight*0.8;
},
прокрутка монитора
Чтобы скрыть эту плавающую кнопку при прокрутке страницы, первым шагом будет прослушивание события прокрутки страницы.
mounted(){
window.addEventListener('scroll', this.handleScrollStart);
},
methods:{
handleScrollStart(){
this.left = document.documentElement.clientWidth - 25;
}
}
Ну и не забудьте отменить регистрацию.
beforeDestroy(){
window.removeEventListener('scroll', this.handleScrollStart);
},
Это заставит компонент сместиться еще на 25 пикселей вправо при прокрутке страницы. но! Я еще не написал анимацию...
анимация перехода
Ну и конечно я не буду использовать js для написания анимаций, мы вcssДобрый.ys-float-btnплюсtransition: all 0.3s;Анимация перехода готова.
Когда прокрутка?
слушалscrollСобытие — это только первый шаг, так когда же остановится событие прокрутки? Браузер не готовит нам такое событие, нам нужно реализовать его вручную. Идея на самом деле очень проста, когда страница обновляется за период времениscrollTopЕсли не меняется, значит, прокрутка страницы прекратилась.
Поэтому нам нужноdataфункция возвращаетtimerОбъект, используемый для хранения нашего таймера. так:
data(){
return{
timer:null,
currentTop:0
}
}
Сделать макияжhandleScrollStartметод.
вызыватьscrollочищает текущий таймер (если он существует) и перезапускает таймер
handleScrollStart(){
this.timer&&clearTimeout(this.timer);
this.timer = setTimeout(()=>{
this.handleScrollEnd();
},300);
this.currentTop = document.documentElement.scrollTop || document.body.scrollTop;
this.left = document.documentElement.clientWidth - 25;
},
Добавлен обратный вызовhandleScrollEndметод
handleScrollEnd(){
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
if(scrollTop === this.currentTop){
this.left = document.documentElement.clientWidth - 50;
clearTimeout(this.timer);
}
}
Если текущая высота прокрутки равна предыдущей высоте прокрутки, это означает, что страница не продолжает прокручиваться. БудуleftОтрегулируйте исходное положение.
О перетаскивании ямы, на которую я наступил
Чтобы реализовать функцию перетаскивания компонентов, первое, о чем я подумал, этоhtml5предоставил намdragметод. Так вот, для нашегоtemplateДобавьте такой код.
<div class="ys-float-btn" :style="{'width':itemWidth+'px','height':itemHeight+'px','left':left+'px','top':top+'px'}"
:draggable ='true' @dragstart="onDragStart" @dragover.prevent = "onDragOver" @dragenter="onDragEnter" @dragend="onDragEnd">
<slot name="icon"></slot>
<p>{{text}}</p>
</div>
В результате во время теста это не действует, и ни один из четырех установленных методов мониторинга не выполняется. Я долго путался, а потом сам нашелbugво время непреднамеренногоchromeОтменил мобильный режим, а затем обнаружил, что метод мониторинга перетаскивания был выполнен.
Это действительно, бессильно жаловаться. Записывать:Мобильный терминал не может использовать перетаскивание для перетаскивания компонентов.
Перетащите на мобильный
Так как же мобильный терминал достигает эффекта перетаскивания? понять, что мобильныйtouchмероприятие.touchа такжеclickПоследовательность срабатывания события следующая:
touchstart => touchmove => touchend => щелчок.
Здесь нам нужно зарегистрировать слушателя для компонента вышеtouchсобытие, как получить конкретноеdomШерстяная ткань?vueпредоставляет намrefАтрибуты.
templateВнешнийdivплюсref
<div class="ys-float-btn" :style="{'left':left+'px','top':top+'px'}"
ref="div">
<slot name="icon"></slot>
<p>{{text}}</p>
</div>
Чтобы убедиться, что компонент успешно смонтирован, мыnextTickрегистрация событий. Теперь метод смонтированного хука выглядит так:
mounted(){
window.addEventListener('scroll', this.handleScrollStart);
this.$nextTick(()=>{
const div = this.$refs.div;
div.addEventListener("touchstart",()=>{
});
div.addEventListener("touchmove",(e)=>{
});
div.addEventListener("touchend",()=>{
});
});
},
В процессе перетаскивания компонента не должно быть овер-анимации компонента, поэтому отменяем овер-анимацию в touchstart.
div.addEventListener("touchstart",()=>{
div.style.transition = 'none';
});
В процессе перетаскивания компонент должен следовать за движением пальца.
div.addEventListener("touchmove",(e)=>{
if (e.targetTouches.length === 1) {//一根手指
let touch = event.targetTouches[0];
this.left = touch.clientX;
this.top = touch.clientY;
}
});
Некоторые студенты, возможно, заметили упущения после прочтения приведенного выше кода, кажется, что приведенный выше код способен заставить компонент двигаться пальцем, но этого недостаточно. Потому что это не центр компонента, который перемещается пальцем. Настроим:
div.addEventListener("touchmove",(e)=>{
if (e.targetTouches.length === 1) {
let touch = event.targetTouches[0];
this.left = touch.clientX - 25;//组件的宽度是50
this.top = touch.clientY - 25;
}
});
После окончания перетаскивания он считается слегка оставленным или слегка правым страницами, повторно отрегулируйте положение компонента и сбросьте чрезмерную анимацию.
div.addEventListener("touchend",()=>{
div.style.transition = 'all 0.3s';
if(this.left>document.documentElement.clientWidth/2){
this.left = document.documentElement.clientWidth - 50;
}else{
this.left = 0;
}
});
Я пишу вам в конце не так ли? Кажется, мы что-то упустили. Кстати, он не определяет, когда страница прокручивается влево или вправо сборки, правильное время в процессе как единое целое. Теперь измените методы handleScrollStart и handleScrollEnd.
handleScrollStart(){
this.timer&&clearTimeout(this.timer);
this.timer = setTimeout(()=>{
this.handleScrollEnd();
},300);
this.currentTop = document.documentElement.scrollTop || document.body.scrollTop;
if(this.left>document.documentElement.clientWidth/2){
this.left = document.documentElement.clientWidth - 25;
}else{
this.left = -25;
}
},
handleScrollEnd(){
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
if(scrollTop === this.currentTop){
if(this.left>document.documentElement.clientWidth/2){
this.left = document.documentElement.clientWidth - 50;
}else{
this.left = 0;
}
clearTimeout(this.timer);
}
}
рефакторинг
Я только что закончил писать этот компонент после удара по клавиатуре, это конец? Нет, конечно нет. Почему мы пишем компоненты? Разве не для повторного использования, сейчас этот компонент забит всякими немаркированными цифрами и повторяющимся кодом, пора рефакторить. При разработке компонентов обычно сначала используются данные, а теперь давайте вернемся назад и посмотрим, какие данные необходимо предопределить.
props:{
text:{
type:String,
default:"默认文字"
},
itemWidth:{
type:Number,
default:60
},
itemHeight:{
type:Number,
default:60
},
gapWidth:{
type:Number,
default:10
},
coefficientHeight:{
type:Number,
default:0.8
}
}
Нам нужны ширина и высота компонента (расстояние от границы страницы) и да, ширина области просмотра! Мы использовали много раз в предыдущей статьеdocument.documentElement.clientWidthНе знаю, устали ли вы читать, но я все равно устал писать...
Данные, используемые внутри компонента, определяются данными:
data(){
return{
timer:null,
currentTop:0,
clientWidth:0,
clientHeight:0,
left:0,
top:0,
}
}
Поэтому нам нужно предварительно обработать эти данные при создании компонента!
СейчасcreatedЭто выглядит так:
created(){
this.clientWidth = document.documentElement.clientWidth;
this.clientHeight = document.documentElement.clientHeight;
this.left = this.clientWidth - this.itemWidth - this.gapWidth;
this.top = this.clientHeight*this.coefficientHeight;
},
... Только здесь, остальное почти там...
Полный исходный код
<template>
<div class="ys-float-btn" :style="{'width':itemWidth+'px','height':itemHeight+'px','left':left+'px','top':top+'px'}"
ref="div"
@click ="onBtnClicked">
<slot name="icon"></slot>
<p>{{text}}</p>
</div>
</template>
<script>
export default {
name: "FloatImgBtn",
props:{
text:{
type:String,
default:"默认文字"
},
itemWidth:{
type:Number,
default:60
},
itemHeight:{
type:Number,
default:60
},
gapWidth:{
type:Number,
default:10
},
coefficientHeight:{
type:Number,
default:0.8
}
},
created(){
this.clientWidth = document.documentElement.clientWidth;
this.clientHeight = document.documentElement.clientHeight;
this.left = this.clientWidth - this.itemWidth - this.gapWidth;
this.top = this.clientHeight*this.coefficientHeight;
},
mounted(){
window.addEventListener('scroll', this.handleScrollStart);
this.$nextTick(()=>{
const div = this.$refs.div;
div.addEventListener("touchstart",()=>{
div.style.transition = 'none';
});
div.addEventListener("touchmove",(e)=>{
if (e.targetTouches.length === 1) {
let touch = event.targetTouches[0];
this.left = touch.clientX - this.itemWidth/2;
this.top = touch.clientY - this.itemHeight/2;
}
});
div.addEventListener("touchend",()=>{
div.style.transition = 'all 0.3s';
if(this.left>this.clientWidth/2){
this.left = this.clientWidth - this.itemWidth - this.gapWidth;
}else{
this.left = this.gapWidth;
}
});
});
},
beforeDestroy(){
window.removeEventListener('scroll', this.handleScrollStart);
},
methods:{
onBtnClicked(){
this.$emit("onFloatBtnClicked");
},
handleScrollStart(){
this.timer&&clearTimeout(this.timer);
this.timer = setTimeout(()=>{
this.handleScrollEnd();
},300);
this.currentTop = document.documentElement.scrollTop || document.body.scrollTop;
if(this.left>this.clientWidth/2){
this.left = this.clientWidth - this.itemWidth/2;
}else{
this.left = -this.itemWidth/2;
}
},
handleScrollEnd(){
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
if(scrollTop === this.currentTop){
if(this.left>this.clientWidth/2){
this.left = this.clientWidth - this.itemWidth - this.gapWidth;
}else{
this.left = this.gapWidth;
}
clearTimeout(this.timer);
}
}
},
data(){
return{
timer:null,
currentTop:0,
clientWidth:0,
clientHeight:0,
left:0,
top:0,
}
}
}
</script>
<style lang="less" scoped>
.ys-float-btn{
background:rgb(255,255,255);
box-shadow:0 2px 10px 0 rgba(0,0,0,0.1);
border-radius:50%;
color: #666666;
z-index: 20;
transition: all 0.3s;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: fixed;
bottom: 20vw;
img{
width: 50%;
height: 50%;
object-fit: contain;
margin-bottom: 3px;
}
p{
font-size:7px;
}
}
</style>