質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
CSS3

CSS(Cascading Style Sheet)の第3版です。CSS3と略されることが多いです。色やデザインを柔軟に変更することが可能になります。

HTML5

HTML5 (Hyper Text Markup Language、バージョン 5)は、マークアップ言語であるHTMLの第5版です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

1回答

9920閲覧

ピンチイン・ピンチアウトで特定の要素だけブラウザの拡大縮小を禁止したい

mmmmmmmmmm

総合スコア16

CSS3

CSS(Cascading Style Sheet)の第3版です。CSS3と略されることが多いです。色やデザインを柔軟に変更することが可能になります。

HTML5

HTML5 (Hyper Text Markup Language、バージョン 5)は、マークアップ言語であるHTMLの第5版です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

1グッド

2クリップ

投稿2019/06/23 23:50

前提・実現したいこと

react-calendar-timelineを使用してサンプルコードを書いているのですが、
このComponentはカレンダー部分でピンチイン・ピンチアウトすると、カレンダーの時間軸がzoom されるようになっています。

実現したい動きは、
①カレンダー部分でピンチイン・ピンチアウトすれば、カレンダーの時間軸がzoomされる。この時ブラウザのzoomは作動しない。
②カレンダー以外の部分でピンチイン・ピンチアウトすれば、ブラウザのzoomが動作する。

例えば、このcodepenのサンプルだとcodepenの部分はブラウザの拡大縮小が動作し、カレンダー上ではカレンダーの拡大縮小が動作します。
まさにこのような動きを実現したいです。
1.カレンダーのところでピンチイン・ピンチアウトしてみてください。
2.カレンダーじゃないところでピンチイン・ピンチアウトしてみてください。
https://codepen.io/xmityaz/pen/wGZRoq

発生している問題・エラーメッセージ

しかしスマートフォンやiPadでピンチイン・ピンチアウトすると、
ブラウザの拡大縮小がカレンダーの時間軸のzoomと同時に動いてしまい、zoomがしずらくなってしまいます。

該当のソースコード

javascript

1import React from 'react'; 2import ReactDOM from 'react-dom'; 3 4 5import Timeline from 'react-calendar-timeline' 6// make sure you include the timeline stylesheet or the timeline will not be styled 7import 'react-calendar-timeline/lib/Timeline.css' 8import moment from 'moment' 9 10const groups = [ 11 { id: 1, title: 'group 1' }, { id: 2, title: 'group 2' }, { id: 3, title: 'group 3' }, 12 { id: 4, title: 'group 4' }, { id: 5, title: 'group 5' }, { id: 6, title: 'group 6' }, 13 ]; 14 15const items = [ 16 {id: 1, group: 1, title: 'item 1', start_time: moment(), end_time: moment().add(1, 'hour')}, 17 {id: 2, group: 2, title: 'item 2', start_time: moment().add(-0.5, 'hour'),end_time: moment().add(0.5, 'hour')}, 18 {id: 3, group: 1, title: 'item 3', start_time: moment().add(2, 'hour'), end_time: moment().add(3, 'hour')} 19] 20 21ReactDOM.render( 22 <div> 23 <div>aaaaaaaaaaaa</div> 24 <div>aaaaaaaaaaaa</div> 25 <div>aaaaaaaaaaaa</div> 26 <div>aaaaaaaaaaaa</div> 27 <div>aaaaaaaaaaaa</div> 28 Rendered by react! 29 <div> 30 <Timeline 31 groups={groups} 32 items={items} 33 defaultTimeStart={moment().add(-12, 'hour')} 34 defaultTimeEnd={moment().add(12, 'hour')} 35 /> 36 </div> 37 38 </div>, 39 document.getElementById('root'));

javascript

1import React from 'react'; 2import ReactDOM from 'react-dom'; 3 4 5import Timeline from 'react-calendar-timeline' 6// make sure you include the timeline stylesheet or the timeline will not be styled 7import 'react-calendar-timeline/lib/Timeline.css' 8import moment from 'moment' 9 10const groups = [ 11 { id: 1, title: 'group 1' }, { id: 2, title: 'group 2' }, { id: 3, title: 'group 3' }, 12 { id: 4, title: 'group 4' }, { id: 5, title: 'group 5' }, { id: 6, title: 'group 6' }, 13 ]; 14 15const items = [ 16 {id: 1, group: 1, title: 'item 1', start_time: moment(), end_time: moment().add(1, 'hour')}, 17 {id: 2, group: 2, title: 'item 2', start_time: moment().add(-0.5, 'hour'),end_time: moment().add(0.5, 'hour')}, 18 {id: 3, group: 1, title: 'item 3', start_time: moment().add(2, 'hour'), end_time: moment().add(3, 'hour')} 19] 20 21ReactDOM.render( 22 <div> 23 <div>aaaaaaaaaaaa</div> 24 <div>aaaaaaaaaaaa</div> 25 <div>aaaaaaaaaaaa</div> 26 Rendered by react! 27 <div> 28 <Timeline 29 groups={groups} 30 items={items} 31 defaultTimeStart={moment().add(-12, 'hour')} 32 defaultTimeEnd={moment().add(12, 'hour')} 33 /> 34 </div> 35 36 </div>, 37 document.getElementById('root'));

html

1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="utf-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> 6 <meta name="theme-color" content="#000000"> 7 <link rel="manifest" href="%PUBLIC_URL%/manifest.json"> 8 <title></title> 9</head> 10<body style="width: 100%"> 11<noscript> 12 You need to enable JavaScript to run this app. 13</noscript> 14<div id="root"></div> 15</body> 16</html>

試したこと

1.user-scalable=noをつけてみる。
→ これはうまくいきませんでした。ブラウザの拡大縮小も禁止できず。

html

1<meta name="viewport" content="initial-scale=1.0, user-scalable=no">

2.カレンダーからのTouchMove、TouchStartイベントをキャンセルする。
これもうまくいきませんでした。

jsx

1ReactDOM.render( 2 <div> 3 <div>aaaaaaaaaaaa</div> 4 <div>aaaaaaaaaaaa</div> 5 <div>aaaaaaaaaaaa</div> 6 Rendered by react! 7 <div 8 onTouchMove={(e) => { 9 e.stopPropagation(); 10 e.preventDefault(); 11 }} 12 onTouchStart={(e) => { 13 e.stopPropagation(); 14 e.preventDefault(); 15 }} 16 > 17 <Timeline 18 groups={groups} 19 items={items} 20 defaultTimeStart={moment().add(-12, 'hour')} 21 defaultTimeEnd={moment().add(12, 'hour')} 22 /> 23 </div> 24 25 </div>, 26 document.getElementById('root'));

補足情報(FW/ツールのバージョンなど)

ここにより詳細な情報を記載してください。

KuwabataK👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

最近のブラウザでは、e.preventDefault()は、イベントリスナのオプションで{passive: false}をつけないと有効にならなくなりました。以下のURLが参考になります。

http://iphone.f-tools.net/html5/Kakudai-Kinsi.html

というわけで、もしもReactで対象のElement内だけでピンチイン/アウトを禁止したい場合は、以下のURLを参考にイベントリスナを登録してやればいけると思います。
Ref と DOM

ただ、refを使ってリスナを登録すること自体、Reactではあまりやりたくないことだと思うので、もう少し良いやり方があるのかもしれません。

一応サンプルコードを載せておきます。
私の環境だと、以下のようなコードを書けばうまく意図した動きになってくれました。

ついでにGitHubにサンプルプロジェクトを作ったので、必要であれば参考にしてください
https://github.com/kuwabataK/react-disable-pinch-in-sample

js

1import React from 'react'; 2import Timeline from 'react-calendar-timeline' 3// make sure you include the timeline stylesheet or the timeline will not be styled 4import 'react-calendar-timeline/lib/Timeline.css' 5import moment from 'moment' 6import './App.css'; 7 8class App extends React.Component { 9 10 constructor(props) { 11 super(props); 12 13 this.groups = [ 14 { id: 1, title: 'group 1' }, { id: 2, title: 'group 2' }, { id: 3, title: 'group 3' }, 15 { id: 4, title: 'group 4' }, { id: 5, title: 'group 5' }, { id: 6, title: 'group 6' }, 16 ]; 17 18 this.items = [ 19 { id: 1, group: 1, title: 'item 1', start_time: moment(), end_time: moment().add(1, 'hour') }, 20 { id: 2, group: 2, title: 'item 2', start_time: moment().add(-0.5, 'hour'), end_time: moment().add(0.5, 'hour') }, 21 { id: 3, group: 1, title: 'item 3', start_time: moment().add(2, 'hour'), end_time: moment().add(3, 'hour') } 22 ] 23 24 // Elementに対するrefを作成し、render内のElementに登録 25 this.myRef = React.createRef(); 26 27 } 28 29 componentDidMount() { 30 // Didmount後にリスナーを登録 31 this.myRef.current.addEventListener('touchstart', this.handleDisablePinchIn, { passive: false }) // {passive: false}を指定しないとe.preventDefault()は有効になってくれない 32 } 33 34 componentWillUnmount() { 35 // unmount前にリスナーを削除 36 this.myRef.current.removeEventListener('touchstart', this.handleDisablePinchIn, { passive: false }) 37 } 38 39 handleDisablePinchIn(e) { 40 if (e.touches.length >= 2) { 41 e.preventDefault() 42 } 43 } 44 45 render() { 46 return <div> 47 <div ref={this.myRef}> 48 <Timeline 49 groups={this.groups} 50 items={this.items} 51 defaultTimeStart={moment().add(-12, 'hour')} 52 defaultTimeEnd={moment().add(12, 'hour')} 53 /> 54 </div> 55 </div> 56 } 57} 58 59export default App; 60

投稿2019/06/25 12:21

編集2019/06/26 06:42
KuwabataK

総合スコア306

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

mmmmmmmmmm

2019/06/25 18:32

ありがとうございます! 先ほど無事本番環境にデプロイしました。 なんとお礼をしていいやら 取り急ぎおかげさまで問題が解決したことのご報告です。 詳しくはまた後ほど追記します。 本当にありがとうございます。
KuwabataK

2019/06/26 06:45

すいません。 もうデプロイされたとのことなので、遅かったかもしれませんが、unmount時にイベントリスナーを削除する処理が抜けていたので追記しました。 今回の場合はよほど頻繁にページ遷移しない限りは問題ないと思いますが、メモリリークの原因になり得るので、もし修正できるなら修正したほうが良いと思います。
mmmmmmmmmm

2019/06/30 09:14 編集

KuwabataKさんこんばんは 追加のコメント大変遅くなりました。 KuwabataKさんからの回答を頂くまでの自分の情報の検索状態は以下の状態でした。 3つの情報を発見 1. touch-action:none; https://qiita.com/SHiMPEi_THE_END/items/0239ca5d5b6ee9435305 2. user-scalable=no 3.document.addEventListener('touchstart', function() {}, {passive: true}); https://blog.webico.work/passive-event-listeber01 この時点で{passive: true}の情報は見かけていたのですが、基本的に全てFunctionalComponentで実装するという方針になっていたため、また、KuwabataKさんのおっしゃる通り「refを使ってリスナを登録すること」もできればしたくなかったのでまだ動くか確証を持てなかった{passive: true}での調査はせずに、touch-action:none;かuser-scalable=noで実験を繰り返していました。 そしてtouch-action:none;もuser-scalable=noもうまく行かずに絶望していたころKuwabataKさんから回答があったことに気づき、「期限内に仕様を実現する」ことが最重要だったため、ここだけcomponentにしてKuwabataKさんのサンプルコードを参考に実装しました。 デプロイした後、動いた時には思わずガッツポーズしました。本当にありがとうございます。 また親切に「unmount時のイベントリスナー削除」の情報までいただきありがとうございます。 お陰様で本番環境では問題なく稼働しています。
KuwabataK

2019/06/30 10:10

丁寧にご報告していただきありがとうございます。 無事、本番環境で可動しているようで良かったです。 いろいろ試されたみたいなので、私が答えなくてもそのうち自己解決できていたかもしれませんね ちなみに、もし、FunctionalComponentで実装したいということであれば、`userEffect()`を使えば同様のことはできると思います。 以下のコードとか参考になりそうです。 https://usehooks.com/useEventListener/ https://codesandbox.io/s/z64on3ypm 余談ですが、JSX上でelementにイベントハンドラを登録するときにoptionを指定できるようにしたいというアイデアは昔からあったようで、以下のissueでいろんな記法が提案されています。 https://github.com/facebook/react/issues/6436 一例を紹介すると以下のような感じです。結局実現はしてないみたいですが・・・ <SomePassiveElement onScroll={{ passive: false, capture: true, handler: this.onScrollThatJustListens, }} />
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問