javascript - semánticas - wrapper html que es
¿Cómo evitar el desplazamiento de documentos pero permitir el desplazamiento dentro de elementos div en sitios web para iOS y Android? (9)
¿Qué tal esta solución de CSS único :
https://jsfiddle.net/Volker_E/jwGBy/24/
body
obtiene la position: fixed;
y cualquier otro elemento que desee un overflow: scroll;
. Funciona en el dispositivo móvil Chrome (WebKit) / Firefox 19 / Opera 12.
También verás mis diversos intentos hacia una solución jQuery. Pero tan pronto como esté vinculando touchmove
/ touchstart
para documentar, obstaculiza el desplazamiento en el div infantil sin importar si está desagregado o no.
Descargo de responsabilidad: ¡En muchos sentidos, las soluciones a este problema no son muy buenas para UX! Nunca sabrá qué tan grande es exactamente la ventana de visualización de sus visitantes o qué tamaño de fuente están usando (al estilo del agente de usuario del cliente), por lo tanto, podría ser fácilmente que el documento oculte contenido importante.
Creé un sitio web con jQueryMobile para iOS y Android.
No quiero que el documento en sí se desplace. En cambio, solo un área (un elemento <div>
) debe ser desplazable (a través de css property overflow-y:scroll
).
Así que desactivé el desplazamiento de documentos a través de:
$(document).bind("touchstart", function(e){
e.preventDefault();
});
$(document).bind("touchmove", function(e){
e.preventDefault();
});
Pero eso también desactivará el desplazamiento para todos los demás elementos en el documento, sin importar si el overflow:scroll
está configurado o no.
¿Como puedo resolver esto?
Aquí está mi implementación que funciona en dispositivos táctiles y computadoras portátiles.
function ScrollManager() {
let startYCoord;
function getScrollDiff(event) {
let delta = 0;
switch (event.type) {
case ''mousewheel'':
delta = event.wheelDelta ? event.wheelDelta : -1 * event.deltaY;
break;
case ''touchstart'':
startYCoord = event.touches[0].clientY;
break;
case ''touchmove'': {
const yCoord = event.touches[0].clientY;
delta = yCoord - startYCoord;
startYCoord = yCoord;
break;
}
}
return delta;
}
function getScrollDirection(event) {
return getScrollDiff(event) >= 0 ? ''UP'' : ''DOWN'';
}
function blockScrollOutside(targetElement, event) {
const { target } = event;
const isScrollAllowed = targetElement.contains(target);
const isTouchStart = event.type === ''touchstart'';
let doScrollBlock = !isTouchStart;
if (isScrollAllowed) {
const isScrollingUp = getScrollDirection(event) === ''UP'';
const elementHeight = targetElement.scrollHeight - targetElement.offsetHeight;
doScrollBlock =
doScrollBlock &&
((isScrollingUp && targetElement.scrollTop <= 0) ||
(!isScrollingUp && targetElement.scrollTop >= elementHeight));
}
if (doScrollBlock) {
event.preventDefault();
}
}
return {
blockScrollOutside,
getScrollDirection,
};
}
const scrollManager = ScrollManager();
const testBlock = document.body.querySelector(''.test'');
function handleScroll(event) {
scrollManager.blockScrollOutside(testBlock, event);
}
window.addEventListener(''scroll'', handleScroll);
window.addEventListener(''mousewheel'', handleScroll);
window.addEventListener(''touchstart'', handleScroll);
window.addEventListener(''touchmove'', handleScroll);
.main {
border: 1px solid red;
height: 200vh;
}
.test {
border: 1px solid green;
height: 300px;
width: 300px;
overflow-y: auto;
position: absolute;
top: 100px;
left: 50%;
}
.content {
height: 100vh;
}
<div class="main">
<div class="test">
<div class="content"></div>
</div>
</div>
Aquí hay una solución que estoy usando:
$ scrollElement es el elemento de desplazamiento, $ scrollMask es un div con position: fixed; top: 0; bottom: 0;
estilo position: fixed; top: 0; bottom: 0;
position: fixed; top: 0; bottom: 0;
. El z-index
de $ scrollMask es más pequeño que $ scrollElement.
$scrollElement.on(''touchmove touchstart'', function (e) {
e.stopPropagation();
});
$scrollMask.on(''touchmove'', function(e) {
e.stopPropagation();
e.preventDefault();
});
Aquí hay una solución que usa jQuery para los eventos.
var stuff = {};
$(''#scroller'').on(''touchstart'',stuff,function(e){
e.data.max = this.scrollHeight - this.offsetHeight;
e.data.y = e.originalEvent.pageY;
}).on(''touchmove'',stuff,function(e){
var dy = e.data.y - e.originalEvent.pageY;
// if scrolling up and at the top, or down and at the bottom
if((dy < 0 && this.scrollTop < 1)||(dy > 0 && this.scrollTop >= e.data.max)){
e.preventDefault();
};
});
En mi caso, tengo un cuerpo desplazable y un menú flotante desplazable sobre él. Ambos tienen que ser desplazables, pero tuve que evitar el desplazamiento del cuerpo cuando el "menú flotante" (posición: fijo) recibía eventos táctiles y se desplazaba hacia arriba o hacia abajo. Por defecto, el navegador comenzó a desplazarse por el cuerpo.
Realmente me gustó la respuesta de jimmont , pero desafortunadamente no funcionó bien en todos los dispositivos y navegadores, especialmente con un desliz rápido y largo.
Terminé usando MOMENTUM SCROLLING USING JQUERY (hnldesign.nl) en el menú flotante, lo que impide el desplazamiento predeterminado del navegador y luego anima a desplazarse por sí mismo. Incluyo ese código aquí para completarlo:
/**
* jQuery inertial Scroller v1.5
* (c)2013 hnldesign.nl
* This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/.
**/
/*jslint browser: true*/
/*global $, jQuery*/
/* SETTINGS */
var i_v = {
i_touchlistener : ''.inertialScroll'', // element to monitor for touches, set to null to use document. Otherwise use quotes. Eg. ''.myElement''. Note: if the finger leaves this listener while still touching, movement is stopped.
i_scrollElement : ''.inertialScroll'', // element (class) to be scrolled on touch movement
i_duration : window.innerHeight * 1.5, // (ms) duration of the inertial scrolling simulation. Devices with larger screens take longer durations (phone vs tablet is around 500ms vs 1500ms). This is a fixed value and does not influence speed and amount of momentum.
i_speedLimit : 1.2, // set maximum speed. Higher values will allow faster scroll (which comes down to a bigger offset for the duration of the momentum scroll) note: touch motion determines actual speed, this is just a limit.
i_handleY : true, // should scroller handle vertical movement on element?
i_handleX : true, // should scroller handle horizontal movement on element?
i_moveThreshold : 100, // (ms) determines if a swipe occurred: time between last updated movement @ touchmove and time @ touchend, if smaller than this value, trigger inertial scrolling
i_offsetThreshold : 30, // (px) determines, together with i_offsetThreshold if a swipe occurred: if calculated offset is above this threshold
i_startThreshold : 5, // (px) how many pixels finger needs to move before a direction (horizontal or vertical) is chosen. This will make the direction detection more accurate, but can introduce a delay when starting the swipe if set too high
i_acceleration : 0.5, // increase the multiplier by this value, each time the user swipes again when still scrolling. The multiplier is used to multiply the offset. Set to 0 to disable.
i_accelerationT : 250 // (ms) time between successive swipes that determines if the multiplier is increased (if lower than this value)
};
/* stop editing here */
//set some required vars
i_v.i_time = {};
i_v.i_elem = null;
i_v.i_elemH = null;
i_v.i_elemW = null;
i_v.multiplier = 1;
// Define easing function. This is based on a quartic ''out'' curve. You can generate your own at http://www.timotheegroleau.com/Flash/experiments/easing_function_generator.htm
if ($.easing.hnlinertial === undefined) {
$.easing.hnlinertial = function (x, t, b, c, d) {
"use strict";
var ts = (t /= d) * t, tc = ts * t;
return b + c * (-1 * ts * ts + 4 * tc + -6 * ts + 4 * t);
};
}
$(i_v.i_touchlistener || document)
.on(''touchstart touchmove touchend'', function (e) {
"use strict";
//prevent default scrolling
e.preventDefault();
//store timeStamp for this event
i_v.i_time[e.type] = e.timeStamp;
})
.on(''touchstart'', function (e) {
"use strict";
this.tarElem = $(e.target);
this.elemNew = this.tarElem.closest(i_v.i_scrollElement).length > 0 ? this.tarElem.closest(i_v.i_scrollElement) : $(i_v.i_scrollElement).eq(0);
//dupecheck, optimizes code a bit for when the element selected is still the same as last time
this.sameElement = i_v.i_elem ? i_v.i_elem[0] == this.elemNew[0] : false;
//no need to redo these if element is unchanged
if (!this.sameElement) {
//set the element to scroll
i_v.i_elem = this.elemNew;
//get dimensions
i_v.i_elemH = i_v.i_elem.innerHeight();
i_v.i_elemW = i_v.i_elem.innerWidth();
//check element for applicable overflows and reevaluate settings
this.i_scrollableY = !!((i_v.i_elemH < i_v.i_elem.prop(''scrollHeight'') && i_v.i_handleY));
this.i_scrollableX = !!((i_v.i_elemW < i_v.i_elem.prop(''scrollWidth'') && i_v.i_handleX));
}
//get coordinates of touch event
this.pageY = e.originalEvent.touches[0].pageY;
this.pageX = e.originalEvent.touches[0].pageX;
if (i_v.i_elem.is('':animated'') && (i_v.i_time.touchstart - i_v.i_time.touchend) < i_v.i_accelerationT) {
//user swiped while still animating, increase the multiplier for the offset
i_v.multiplier += i_v.i_acceleration;
} else {
//else reset multiplier
i_v.multiplier = 1;
}
i_v.i_elem
//stop any animations still running on element (this enables ''tap to stop'')
.stop(true, false)
//store current scroll positions of element
.data(''scrollTop'', i_v.i_elem.scrollTop())
.data(''scrollLeft'', i_v.i_elem.scrollLeft());
})
.on(''touchmove'', function (e) {
"use strict";
//check if startThreshold is met
this.go = (Math.abs(this.pageX - e.originalEvent.touches[0].pageX) > i_v.i_startThreshold || Math.abs(this.pageY - e.originalEvent.touches[0].pageY) > i_v.i_startThreshold);
})
.on(''touchmove touchend'', function (e) {
"use strict";
//check if startThreshold is met
if (this.go) {
//set animpar1 to be array
this.animPar1 = {};
//handle events
switch (e.type) {
case ''touchmove'':
this.vertical = Math.abs(this.pageX - e.originalEvent.touches[0].pageX) < Math.abs(this.pageY - e.originalEvent.touches[0].pageY); //find out in which direction we are scrolling
this.distance = this.vertical ? this.pageY - e.originalEvent.touches[0].pageY : this.pageX - e.originalEvent.touches[0].pageX; //determine distance between touches
this.acc = Math.abs(this.distance / (i_v.i_time.touchmove - i_v.i_time.touchstart)); //calculate acceleration during movement (crucial)
//determine which property to animate, reset animProp first for when no criteria is matched
this.animProp = null;
if (this.vertical && this.i_scrollableY) { this.animProp = ''scrollTop''; } else if (!this.vertical && this.i_scrollableX) { this.animProp = ''scrollLeft''; }
//set animation parameters
if (this.animProp) { this.animPar1[this.animProp] = i_v.i_elem.data(this.animProp) + this.distance; }
this.animPar2 = { duration: 0 };
break;
case ''touchend'':
this.touchTime = i_v.i_time.touchend - i_v.i_time.touchmove; //calculate touchtime: the time between release and last movement
this.i_maxOffset = (this.vertical ? i_v.i_elemH : i_v.i_elemW) * i_v.i_speedLimit; //(re)calculate max offset
//calculate the offset (the extra pixels for the momentum effect
this.offset = Math.pow(this.acc, 2) * (this.vertical ? i_v.i_elemH : i_v.i_elemW);
this.offset = (this.offset > this.i_maxOffset) ? this.i_maxOffset : this.offset;
this.offset = (this.distance < 0) ? -i_v.multiplier * this.offset : i_v.multiplier * this.offset;
//if the touchtime is low enough, the offset is not null and the offset is above the offsetThreshold, (re)set the animation parameters to include momentum
if ((this.touchTime < i_v.i_moveThreshold) && this.offset !== 0 && Math.abs(this.offset) > (i_v.i_offsetThreshold)) {
if (this.animProp) { this.animPar1[this.animProp] = i_v.i_elem.data(this.animProp) + this.distance + this.offset; }
this.animPar2 = { duration: i_v.i_duration, easing : ''hnlinertial'', complete: function () {
//reset multiplier
i_v.multiplier = 1;
}};
}
break;
}
// run the animation on the element
if ((this.i_scrollableY || this.i_scrollableX) && this.animProp) {
i_v.i_elem.stop(true, false).animate(this.animPar1, this.animPar2);
}
}
});
Otra observación: también probé varias combinaciones de e.stopPropagation () en el menú div y e.preventDefault () en la ventana / cuerpo en el evento touchmove, pero sin éxito, solo logré evitar el desplazamiento que quería y no el desplazamiento, no quería . También traté de tener un div en todo el documento, con índice z entre el documento y el menú, visible solo entre touchstart y touchend, pero no recibió el evento touchmove (porque estaba en el menú div).
Estaba buscando una solución que no requiriera llamar áreas específicas que deberían desplazarse. Recopilando algunos recursos, esto es lo que funcionó para mí:
// Detects if element has scroll bar
$.fn.hasScrollBar = function() {
return this.get(0).scrollHeight > this.outerHeight();
}
$(document).on("touchstart", function(e) {
var $scroller;
var $target = $(e.target);
// Get which element could have scroll bars
if($target.hasScrollBar()) {
$scroller = $target;
} else {
$scroller = $target
.parents()
.filter(function() {
return $(this).hasScrollBar();
})
.first()
;
}
// Prevent if nothing is scrollable
if(!$scroller.length) {
e.preventDefault();
} else {
var top = $scroller[0].scrollTop;
var totalScroll = $scroller[0].scrollHeight;
var currentScroll = top + $scroller[0].offsetHeight;
// If at container edge, add a pixel to prevent outer scrolling
if(top === 0) {
$scroller[0].scrollTop = 1;
} else if(currentScroll === totalScroll) {
$scroller[0].scrollTop = top - 1;
}
}
});
Este código requiere jQuery.
Fuentes:
- esta publicación
- https://github.com/luster-io/prevent-overscroll
- ¿Cómo puedo verificar si una barra de desplazamiento está visible?
- Jquery comprueba si alguno de los padres div tiene barra de desplazamiento
Actualizar
Necesitaba una versión vainilla de JavaScript de esto, así que la siguiente es una versión modificada. Implementé un verificador de márgenes y algo que explícitamente permite hacer clic en input / textareas (me encontré con problemas con esto en el proyecto en el que lo usé ... puede que no sea necesario para su proyecto). Tenga en cuenta que este es el código ES6.
const preventScrolling = e => {
const shouldAllowEvent = element => {
// Must be an element that is not the document or body
if(!element || element === document || element === document.body) {
return false;
}
// Allow any input or textfield events
if([''INPUT'', ''TEXTAREA''].indexOf(element.tagName) !== -1) {
return true;
}
// Get margin and outerHeight for final check
const styles = window.getComputedStyle(element);
const margin = parseFloat(styles[''marginTop'']) +
parseFloat(styles[''marginBottom'']);
const outerHeight = Math.ceil(element.offsetHeight + margin);
return (element.scrollHeight > outerHeight) && (margin >= 0);
};
let target = e.target;
// Get first element to allow event or stop
while(target !== null) {
if(shouldAllowEvent(target)) {
break;
}
target = target.parentNode;
}
// Prevent if no elements
if(!target) {
e.preventDefault();
} else {
const top = target.scrollTop;
const totalScroll = target.scrollHeight;
const currentScroll = top + target.offsetHeight;
// If at container edge, add a pixel to prevent outer scrolling
if(top === 0) {
target.scrollTop = 1;
} else if(currentScroll === totalScroll) {
target.scrollTop = top - 1;
}
}
};
document.addEventListener(''touchstart'', preventScrolling);
document.addEventListener(''mousedown'', preventScrolling);
Finalmente, lo tengo para trabajar. Muy simple:
var $layer = $("#layer");
$layer.bind(''touchstart'', function (ev) {
var $this = $(this);
var layer = $layer.get(0);
if ($this.scrollTop() === 0) $this.scrollTop(1);
var scrollTop = layer.scrollTop;
var scrollHeight = layer.scrollHeight;
var offsetHeight = layer.offsetHeight;
var contentHeight = scrollHeight - offsetHeight;
if (contentHeight == scrollTop) $this.scrollTop(scrollTop-1);
});
Primero coloca el innerScroller donde quieras en la pantalla y luego arregla outerScroller poniéndolo css en ''hidden''. Cuando desee restaurarlo, puede volver a establecerlo en "automático" o "desplazarse", cualquiera que haya utilizado anteriormente.
Tal vez malinterpreté la pregunta, pero si estoy en lo correcto:
No desea desplazarse excepto un elemento determinado para que:
$(document).bind("touchmove", function(e){
e.preventDefault();
});
Prevenir todo dentro del documento.
¿Por qué no paras el burbujeo del evento en el elemento donde deseas desplazarte? (PD: no es necesario que evite el inicio táctil -> si utiliza el inicio táctil para seleccionar elementos en lugar de los clics que también se evitan, toque mover solo es necesario porque de hecho está rastreando el movimiento)
$(''#element'').on(''touchmove'', function (e) {
e.stopPropagation();
});
Ahora en el elemento CSS
#element {
overflow-y: scroll; // (vertical)
overflow-x: hidden; // (horizontal)
}
Si está en un dispositivo móvil, puede incluso ir un paso más allá. Puede forzar el desplazamiento acelerado por hardware (aunque no todos los navegadores móviles lo admiten);
Browser Overflow scroll:
Android Browser Yes
Blackberry Browser Yes
Chrome for Mobile Yes
Firefox Mobile Yes
IE Mobile Yes
Opera Mini No
Opera Mobile Kinda
Safari Yes
#element.nativescroll {
-webkit-overflow-scrolling: touch;
}
normal:
<div id="element"></div>
sensación nativa:
<div id="element" class="nativescroll"></div>