IOSおよびAndroid用のjQueryMobileを使用してWebサイトを作成しました。
文書自体をスクロールさせたくありません。代わりに、領域(<div>
要素)のみがスクロール可能(cssプロパティoverflow-y:scroll
経由)である必要があります。
だから私は経由でドキュメントのスクロールを無効にしました:
$(document).bind("touchstart", function(e){
e.preventDefault();
});
$(document).bind("touchmove", function(e){
e.preventDefault();
});
ただし、overflow:scroll
が設定されているかどうかに関係なく、ドキュメント内の他のすべての要素のスクロールも無効になります。
どうすれば解決できますか?
最後に、私はそれを動作させました。本当に簡単:
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);
});
これはどうですかCSSのみソリューション:
https://jsfiddle.net/Volker_E/jwGBy/24/
body
取得position: fixed;
および希望する他のすべての要素overflow: scroll;
。モバイルで動作するChrome(WebKit)/ Firefox 19/Opera 12。
また、jQueryソリューションに向けた私のさまざまな試みもご覧いただけます。ただし、touchmove
/touchstart
をドキュメントにバインドするとすぐに、バインドされていないかどうかに関係なく、子divのスクロールが妨げられます。
免責事項:この問題の解決策は、多くの点で基本的にはUX的にはあまり良いものではありません!ビジターのビューポートの大きさや、使用しているフォントサイズ(クライアントユーザーエージェントスタイルのような)が正確にどれだけ大きいかがわからないため、重要なコンテンツがドキュメント内で隠されている可能性があります。
たぶん私は質問を誤解したかもしれませんが、私が正しいなら:
特定の要素以外をスクロールできないようにするには、次のようにします。
$(document).bind("touchmove", function(e){
e.preventDefault();
});
ドキュメント内のすべてを防止します。
スクロールしたい要素でイベントバブリングを停止しないのはなぜですか? (PS:タッチスタートを防止する必要はありません->防止されるクリックの代わりに要素を選択するためにタッチスタートを使用する場合、実際に動きをトレースしているため、タッチの移動のみが必要です) =
$('#element').on('touchmove', function (e) {
e.stopPropagation();
});
次に、要素CSS
#element {
overflow-y: scroll; // (vertical)
overflow-x: hidden; // (horizontal)
}
モバイルデバイスを使用している場合は、さらに一歩進めることもできます。ハードウェアアクセラレーションによるスクロールを強制できます(ただし、すべてのモバイルブラウザーがこれをサポートしているわけではありません)。
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;
}
通常:
<div id="element"></div>
ネイティブ感:
<div id="element" class="nativescroll"></div>
スクロールする特定の領域を呼び出す必要のないソリューションを探していました。いくつかのリソースをつなぎ合わせて、ここに私にとってうまくいったものがあります:
// 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;
}
}
});
このコードにはjQueryが必要です。
ソース:
更新
このバニラJavaScriptバージョンが必要だったので、以下は修正バージョンです。私はマージンチェッカーと入力/テキストエリアを明示的にクリック可能にするものを実装しました(使用したプロジェクトでこれに問題が発生していました...あなたのプロジェクトには必要ないかもしれません)。これは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);
私の場合、スクロール可能な本体とその上にスクロール可能なフローティングメニューがあります。どちらもスクロール可能である必要がありますが、「フローティングメニュー」(位置:固定)がタッチイベントを受信してスクロールし、上部または下部に達したときに、本体のスクロールを防止する必要がありました。デフォルトでは、ブラウザは本文のスクロールを開始しました。
jimmont's answer が本当に好きでしたが、残念なことに、すべてのデバイスとブラウザー、特に高速で長いスワイプではうまく動作しませんでした。
最終的には、フローティングメニューで MOQUEUM SCROLLING USING JQUERY(hnldesign.nl) を使用することになりました。完全を期すために、ここに そのコード を含めます。
/**
* 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);
}
}
});
別の観察:touchmoveイベントで、メニューdivのe.stopPropagation()とwindow/bodyのe.preventDefault()のさまざまな組み合わせを試しましたが、成功せずに、スクロールしたくても、スクロールしたくはありませんでした。 。また、ドキュメントとメニューの間にz-indexがあり、touchstartとtouchendの間にのみ表示される、ドキュメント全体にdivを配置しようとしましたが、touchmoveイベントを受信しませんでした(menu divの下にあったため)。
私が使用しているソリューションは次のとおりです。
$ scrollElementはスクロール要素です。$ scrollMaskはposition: fixed; top: 0; bottom: 0;
スタイルのdivです。 $ scrollMaskのz-index
は、$ scrollElementより小さいです。
$scrollElement.on('touchmove touchstart', function (e) {
e.stopPropagation();
});
$scrollMask.on('touchmove', function(e) {
e.stopPropagation();
e.preventDefault();
});
まず、画面上の任意の場所にinnerScrollerを配置し、cssを「hidden」に設定してouterScrollerを修正します。復元する場合は、以前に使用した「自動」または「スクロール」に戻すことができます。
これは、タッチデバイスとラップトップで動作する私の実装です。
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>
これは、AndroidおよびIOSデバイス。
スクロールしたくないdiv class="backdrop">
要素があると想像してください。しかし、このbackdrop
の上にある要素をスクロールできるようにしたいのです。
function handleTouchMove(event) {
const [backdrop] = document.getElementsByClassName('backdrop');
const isScrollingBackdrop = backdrop === event.target;
isScrollingBackdrop ? event.preventDefault() : event.stopPropagation();
}
window.addEventListener('touchmove', handleTouchMove, { passive: false });
そのため、touchmove
イベントをリッスンします。背景の上をスクロールしている場合、それを防ぎます。他のものをスクロールしている場合、それを許可しますが、その伝播を停止して、backdrop
もスクロールしないようにします。
もちろん、これは非常に基本的なものであり、多くの作業を繰り返して拡張できますが、これがVueJs2プロジェクトの問題を解決した理由です。
それが役に立てば幸い! ;)
次に、イベントにjQueryを使用するソリューションを示します。
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();
};
});