Search code examples
javascriptjqueryhtmlcssmaterialize

How to disable pointer-events, but allow drag


I'm trying to create a sidebar menu for the mobile web page. My goal is to slide-out side-bar from 25% of screens left edge.

Sidebar menu drag target

A similar way is used in Materialize frameworks Sidenav

#drag-target is used to receive drag events and slide-out sidebar. Problem is that #drag-target covers part of the content and blocks click/touch events on underlying elements.

I used CSS rule pointer-events: none but this breaks swipe on the element itself.

Is there any way to pass touch/click events through the #drag-target on all underlying elements, except swipe/drag?


Solution

  • As it's not possible to catch events with pointer-events: none applied, I ran to another way by handling events on the whole body and manually sliding side-bar.

    After struggling with Hammer.js I came with working side-bar but found out that, Hammer has a little bug with Pan events and sometimes event was giving incorrect delta. So I decided to listen to basic TouchEvents.

    Here is the result if it will be helpful to anyone: https://codepen.io/pen/xxbNwmX

    P.S. Test it from Touch-enabled devices or browser emulator

    /**
     * @class Sidenav
     * @constructor
     */
    class Sidenav {
    	/**
    	 * @param wrapper {String | jQuery}
    	 * @param sidenav {String | jQuery}
    	 * @param [hitArea] {number}
    	 * @param [threshold] {number}
    	 */
    	constructor (wrapper, sidenav, hitArea = .20, threshold = 20) {
    		// settings
    		this.wrapper = wrapper
    		this.sidenav = sidenav
    		this.hitArea = window.innerWidth * hitArea
    		this.threshold = threshold
    		this.width = $(sidenav).width()
    		this.isHitarea = false
    		this._state = 'closed'
    		this.states = 'open closed open-started close-started'
    		this.touchStart = 0
    		
    		// touch start
    		$(this.wrapper).on('touchstart', (e) => {
    			let touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0]
    			this.touchStart = touch.pageX
    			this.isHitarea = this.touchStart < this.hitArea
    		})
    		
    		// touch move
    		$(this.wrapper).on('touchmove', (e) => {
    			let touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0]
    			let delta = touch.pageX - this.touchStart
    			this.slide(delta)
    		})
    		
    		// touch end
    		$(this.wrapper).on('touchend', (e) => {
    			let touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0]
    			let delta = touch.pageX - this.touchStart
    			
    			if (this.state == 'open-started') {
    				this.state = Math.abs(delta) >= this.width / 4 ? 'open' : 'closed'
    			}
    			else if (this.state == 'close-started') {
    				this.state = Math.abs(delta) >= this.width / 4 ? 'closed' : 'open'
    			}
    		})
    		
    	}
    	
    	/** @param st {String} */
    	set state (st) {
    		this._state = st
    		
    		// remove all classes
    		$(this.wrapper).removeClass(this.states).addClass(st)
    	}
    	
    	get state () {
    		return this._state
    	}
    	
    	slide (delta) {
    		switch (this.state) {
    			case 'closed':
    				if (this.isHitarea && delta >= this.threshold) {
    					this.state = 'open-started'
    					this.move(delta - this.threshold)
    				}
    				break
    			case 'open-started':
    				this.move(delta - this.threshold)
    				break
    			case 'open':
    				if (delta < 0 && Math.abs(delta) > this.threshold) {
    					this.state = 'close-started'
    					let translate = Math.max(-this.width, delta + this.threshold)
    					this.move(translate, 1)
    				}
    				break
    			case 'close-started':
    				let translate = Math.min(Math.max(-this.width, delta + this.threshold), 0)
    				this.move(translate, 1)
    				break
    		}
    	}
    	
    	move (delta, left = false) {
    		if (left) {
    			$(this.wrapper).css('transform', `translateX(${delta}px)`)
    		}
    		else {
    			$(this.wrapper).css('transform', `translateX(${Math.min(-this.width + delta, 0)}px)`)
    		}
    	}
    }
    
    
    // run
    $(function() {
    	let sidenav = new Sidenav('.wrapper', '.sidenav')
    })
    * {
        margin: 0;
        padding: 0
    }
    
    html,body {
        width: 100%;
        height: 100%;
        overflow: hidden
    }
    
    body {
        background-color: #202126;
        font-family: Consolas, sans-serif;
        color: antiquewhite
    }
    
    .wrapper {
        position: relative;
        height: 100%;
        touch-action: pan-x !important;
        transform: translateX(-240px)
    }
    
    .wrapper .sidenav {
        position: fixed;
        left: 0;
        top: 0;
        width: 240px;
        height: 100%;
        background-color: antiquewhite;
        color: #202126;
        box-sizing: border-box;
        padding: 10px
    }
    
    .wrapper .content {
        height: 100%;
        transform: translateX(240px);
        padding: 10px;
        overflow-y: scroll;
        overflow-x: hidden
    }
    
    .wrapper .content button {
        padding: 10px
    }
    
    .wrapper.open {
        transform: translateX(0) !important;
        transition: transform .2s ease-in-out
    }
    
    .wrapper.closed {
        transform: translateX(-240px) !important;
        transition: transform .2s ease-in-out
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <div class="wrapper closed">
      <div class="sidenav">
        <h2>SIDENAV</h2>
      </div>
      <div class="content">
        <h2>CONTENT</h2>
        <p>lorem ipsum dolor sit amet lorem ipsum dolor sit amet lorem ipsum dolor sit amet lorem ipsum dolor sit amet</p>
        <br>
        <button onclick="alert('Click click!')">Clickable button</button>
        <br>
        <br>
        <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus assumenda consequatur debitis doloribus, dolorum esse incidunt nobis obcaecati omnis possimus quasi, quidem recusandae reiciendis rerum tempora unde velit vitae voluptates.</p>
        <br>
        <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus assumenda consequatur debitis doloribus, dolorum esse incidunt nobis obcaecati omnis possimus quasi, quidem recusandae reiciendis rerum tempora unde velit vitae voluptates.</p>
        <br>
        <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus assumenda consequatur debitis doloribus, dolorum esse incidunt nobis obcaecati omnis possimus quasi, quidem recusandae reiciendis rerum tempora unde velit vitae voluptates.</p>
        <br>
        <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus assumenda consequatur debitis doloribus, dolorum esse incidunt nobis obcaecati omnis possimus quasi, quidem recusandae reiciendis rerum tempora unde velit vitae voluptates.</p>
        <br>
        <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus assumenda consequatur debitis doloribus, dolorum esse incidunt nobis obcaecati omnis possimus quasi, quidem recusandae reiciendis rerum tempora unde velit vitae voluptates.</p>
        <br>
        <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus assumenda consequatur debitis doloribus, dolorum esse incidunt nobis obcaecati omnis possimus quasi, quidem recusandae reiciendis rerum tempora unde velit vitae voluptates.</p>
        <br>
        <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus assumenda consequatur debitis doloribus, dolorum esse incidunt nobis obcaecati omnis possimus quasi, quidem recusandae reiciendis rerum tempora unde velit vitae voluptates.</p>
        <br>
      </div>
    </div>