или
Prevent background body scroll in IOS
Много наслышаны про задачу запретить скроллить фон при открытых диалогах и модальных окон. Много крови попило данное решение Apple встроить собственную поддержку прокрутки. При чем от версии к версии изобретают и падают существующие решения.
Причем данная ситуация воспроизводится только на самих устройствах Iphone либо в эмуляторах. Вы не сможете воспроизвести ее в Chrome debugger emulator
Дело в том что body в большинстве своем на сайте - имеет прокрутку. т/к/ высота контента как правило занимает больше одного экрана. Следовательно - вы можете его скроллить. И исчезает ползунок для скролла с виду не сразу и пока он активен - вы сможете за него ухватиться и продолжить скроллинг, чего бы не было наложено поверх данного слоя с прокруткой.
Все думают, что повесив overlay - смогут запретить доступ к фону, а не тут то было. Apple всех перехитрил.
Получается, что единственный способ запретить скроллинг фона - это убрать скроллинг этого самого слоя. Делается это легко. Overflow-y: none;
Авторы vuetify тоже так думали когда встраивали данный стиль
html.overflow-y-hidden {
overflow-y: hidden !important;
}
и что? Думали Apple так прост. Не тут то было
Хорошо, скажите вы. Это же не единственный способ запретить прокрутку
Давайте добавим
body{
position: fixed;
}
И это зашло. Да прокрутка исчезла. Выкуси, создатель движка скроллинга apple
Мы можем при открытии диалога - вешать такой стиль на тело и оно будет заблокировано для скроллинга
Только каждый раз когда мы так делаем - скроллинг сбрасывается и страница прыгает в самый верх. Это не приятно и не красиво
У нас же Javascript, неужели он не достаточно мощный чтобы исправить все эти неисправности?
Давайте запоминать позицию и возвращаться к ней в момент открытия и закрытия диалога. Сказано - сделано
Показываю код на примере mixin для vue с vuetify
export default {
data() {
return {
dialogScroll: 0,
}
},
watch: {
dialog(value) {
if (value) {
this.dialogScroll = window.scrollY
document.body.classList.add('fixed')
} else {
this.$nextTick(() => window.scrollTo({ top: this.dialogScroll }))
document.body.classList.remove('fixed')
}
},
},
}
Само собой
.fixed {
position: fixed;
}
Вы все равно не будете это проверять, вам слишком лень. А мне лень создавать рабочие примеры. Просто достаточно посмотреть на код чтобы понять что оно будет работать как задумано.
Зачем nextTick? Просто возврат должен проходить после того как мы уберем fixed с body, а иначе оно не будет возвращаться
Работает. ДА оно работает. Javascript победил
Только дергается как-то оно. Диалог открывается через анимацию и в это время видно что страница дергается. Очень не приятно. Чем мы можем ответить на это?
Конечно мы можем сместить страницу. Первое что приходит в голову. Но как?
Можем сместить контент через transform: Transition
И оно смещает, но смещает вместе с диалогом, чего не хотелось бы
Мы также можем просто сместить содержание body через отступ. Хорошая мысль
export default {
data() {
return {
dialogScroll: 0,
}
},
watch: {
dialog(value) {
if (value) {
this.dialogScroll = window.scrollY
document.body.classList.add('fixed')
document.body.style.marginTop = '-'+this.dialogScroll+'px'
} else {
this.$nextTick(() => window.scrollTo({ top: this.dialogScroll }))
document.body.classList.remove('fixed')
document.body.style.marginTop = null
}
},
},
}
И еще обработчик на случай если у вас диалог может исчезнуть при переходе назад или при переходе по ссылке с уничтожением диалога. Конечный вариант скрипта выглядит вот так со всеми бантиками
export default {
data() {
return {
dialogScroll: 0,
}
},
beforeDestroy() {
this.scrollRelease()
},
methods:{
scrollLock(){
this.dialogScroll = window.scrollY
document.body.classList.add('fixed')
document.body.style.marginTop = '-'+this.dialogScroll+'px'
},
scrollRelease(){
this.$nextTick(() => window.scrollTo({ top: this.dialogScroll }))
document.body.classList.remove('fixed')
document.body.style.marginTop = null
}
},
watch: {
dialog(value) {
if (value) {
this.scrollLock()
} else {
this.scrollRelease()
}
},
},
}
Да это помогло. Сейчас всё гладко, за исключением случаев когда у вас диалоги открываются через vuex, там уже другая подвязка через mapState
Нужно просто реактивно подвязываться не через v-model а через :value и @input
Но это уже другая тема
<v-dialog
:value="dialog"
@input="value => $store.commit('SET_DIALOG', value)"
>
Надеюсь, помог
Успехов в покорении изысков IOS
Если вам удалось решить проблему по другому - накидывайте на вентилятор в комментариях