您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Jquery plugin for unified mouse and touch events
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/6421/24600/jqueryeventue.js
/* * Jquery plugin for unified mouse and touch events * * Copyright (c) 2013 Michael S. Mikowski * (mike[dot]mikowski[at]gmail[dotcom]) * * Dual licensed under the MIT or GPL Version 2 * http://jquery.org/license * * Versions * 0.3.0 - Initial jQuery plugin site release * - Replaced scrollwheel zoom with drag motion. * This resolved a conflict with scrollable areas. * 0.3.1 - Change for jQuery plugins site * 0.3.2 - Updated to jQuery 1.9.1. * Confirmed 1.7.0-1.9.1 compatibility. * */ /*jslint browser : true, continue : true, devel : true, indent : 2, maxerr : 50, newcap : true, plusplus : true, regexp : true, sloppy : true, vars : true, white : true */ /*global jQuery, sl */ (function ( $ ) { //---------------- BEGIN MODULE SCOPE VARIABLES -------------- var $Special = $.event.special, // shortcut for special event motionMapMap = {}, // map of pointer motions by cursor isMoveBound = false, // flag if move handlers bound pxPinchZoom = -1, // distance between pinch-zoom points optionKey = 'ue_bound', // data key for storing options doDisableMouse = false, // flag to discard mouse input defaultOptMap = { // Default option hash bound_ns_map : {}, // namspace hash e.g. bound_ns_map.utap.fred wheel_ratio : 15, // multiplier for mousewheel delta px_radius : 3, // 'distance' dragged before dragstart ignore_class : ':input', // 'not' suppress matching elements tap_time : 200, // millisecond max time to consider tap held_tap_time : 300 // millisecond min time to consider taphold }, callbackList = [], // global callback stack zoomMouseNum = 1, // multiplier for mouse zoom zoomTouchNum = 4, // multiplier for touch zoom boundList, Ue, motionDragId, motionHeldId, motionDzoomId, motion1ZoomId, motion2ZoomId, checkMatchVal, removeListVal, pushUniqVal, makeListPlus, fnHeld, fnMotionStart, fnMotionMove, fnMotionEnd, onMouse, onTouch, onMousewheel ; //----------------- END MODULE SCOPE VARIABLES --------------- //------------------- BEGIN UTILITY METHODS ------------------ // Begin utiltity /makeListPlus/ // Returns an array with much desired methods: // * remove_val(value) : remove element that matches // the provided value. Returns number of elements // removed. // * match_val(value) : shows if a value exists // * push_uniq(value) : pushes a value onto the stack // iff it does not already exist there // Note: the reason I need this is to compare objects to // objects (perhaps jQuery has something similar?) checkMatchVal = function ( data ) { var match_count = 0, idx; for ( idx = this.length; idx; 0 ) { if ( this[--idx] === data ) { match_count++; } } return match_count; }; removeListVal = function ( data ) { var removed_count = 0, idx; for ( idx = this.length; idx; 0 ) { if ( this[--idx] === data ) { this.splice(idx, 1); removed_count++; idx++; } } return removed_count; }; pushUniqVal = function ( data ) { if ( checkMatchVal.call(this, data ) ) { return false; } this.push( data ); return true; }; // primary utility makeListPlus = function ( input_list ) { if ( input_list && $.isArray(input_list) ) { if ( input_list.remove_val ) { console.warn( 'The array appears to already have listPlus capabilities' ); return input_list; } } else { input_list = []; } input_list.remove_val = removeListVal; input_list.match_val = checkMatchVal; input_list.push_uniq = pushUniqVal; return input_list; }; // End utility /makeListPlus/ //-------------------- END UTILITY METHODS ------------------- //--------------- BEGIN JQUERY SPECIAL EVENTS ---------------- // Unique array for bound objects boundList = makeListPlus(); // Begin define special event handlers Ue = { setup : function( data, a_names, fn_bind ) { var elem_this = this, $to_bind = $(elem_this), seen_map = {}, option_map, idx, namespace_key, ue_namespace_code, namespace_list ; // if previous related event bound do not rebind, but do add to // type of event bound to this element, if not already noted if ( $.data( this, optionKey ) ) { return; } option_map = {}; $.extend( true, option_map, defaultOptMap ); $.data( elem_this, optionKey, option_map ); namespace_list = makeListPlus(a_names.slice(0)); if ( ! namespace_list.length || namespace_list[0] === "" ) { namespace_list = ["000"]; } NSPACE_00: for ( idx = 0; idx < namespace_list.length; idx++ ) { namespace_key = namespace_list[idx]; if ( ! namespace_key ) { continue NSPACE_00; } if ( seen_map.hasOwnProperty(namespace_key) ) { continue NSPACE_00; } seen_map[namespace_key] = true; ue_namespace_code = '.__ue' + namespace_key; $to_bind.bind( 'mousedown' + ue_namespace_code, onMouse ); $to_bind.bind( 'touchstart' + ue_namespace_code, onTouch ); $to_bind.bind( 'mousewheel' + ue_namespace_code, onMousewheel ); } boundList.push_uniq( elem_this ); // record as bound element if ( ! isMoveBound ) { // console.log('first element bound - adding global binds'); $(document).bind( 'mousemove.__ue', onMouse ); $(document).bind( 'touchmove.__ue', onTouch ); $(document).bind( 'mouseup.__ue' , onMouse ); $(document).bind( 'touchend.__ue' , onTouch ); isMoveBound = true; } }, // arg_map.type = string - name of event to bind // arg_map.data = poly - whatever (optional) data was passed when binding // arg_map.namespace = string - A sorted, dot-delimited list of namespaces // specified when binding the event // arg_map.handler = fn - the event handler the developer wishes to be bound // to the event. This function should be called whenever the event // is triggered // arg_map.guid = number - unique ID for event handler, provided by jQuery // arg_map.selector = string - selector used by 'delegate' or 'live' jQuery // methods. Only available when these methods are used. // // this - the element to which the event handler is being bound // this always executes immediate after setup (if first binding) add : function ( arg_map ) { var elem_this = this, option_map = $.data( elem_this, optionKey ), namespace_str = arg_map.namespace, event_type = arg_map.type, bound_ns_map, namespace_list, idx, namespace_key ; if ( ! option_map ) { return; } bound_ns_map = option_map.bound_ns_map; if ( ! bound_ns_map[event_type] ) { // this indicates a non-namespaced entry bound_ns_map[event_type] = {}; } if ( ! namespace_str ) { return; } namespace_list = namespace_str.split('.'); for ( idx = 0; idx < namespace_list.length; idx++ ) { namespace_key = namespace_list[idx]; bound_ns_map[event_type][namespace_key] = true; } }, remove : function ( arg_map ) { var elem_bound = this, option_map = $.data( elem_bound, optionKey ), bound_ns_map = option_map.bound_ns_map, event_type = arg_map.type, namespace_str = arg_map.namespace, namespace_list, idx, namespace_key ; if ( ! bound_ns_map[event_type] ) { return; } // No namespace(s) provided: // Remove complete record for custom event type (e.g. utap) if ( ! namespace_str ) { delete bound_ns_map[event_type]; return; } // Namespace(s) provided: // Remove namespace flags from each custom event typei (e.g. utap) // record. If all claimed namespaces are removed, remove // complete record. namespace_list = namespace_str.split('.'); for ( idx = 0; idx < namespace_list.length; idx++ ) { namespace_key = namespace_list[idx]; if (bound_ns_map[event_type][namespace_key]) { delete bound_ns_map[event_type][namespace_key]; } } if ( $.isEmptyObject( bound_ns_map[event_type] ) ) { delete bound_ns_map[event_type]; } }, teardown : function( a_names ) { var elem_bound = this, $bound = $(elem_bound), option_map = $.data( elem_bound, optionKey ), bound_ns_map = option_map.bound_ns_map, idx, namespace_key, ue_namespace_code, namespace_list ; // do not tear down if related handlers are still bound if ( ! $.isEmptyObject( bound_ns_map ) ) { return; } namespace_list = makeListPlus(a_names); namespace_list.push_uniq('000'); NSPACE_01: for ( idx = 0; idx < namespace_list.length; idx++ ) { namespace_key = namespace_list[idx]; if ( ! namespace_key ) { continue NSPACE_01; } ue_namespace_code = '.__ue' + namespace_key; $bound.unbind( 'mousedown' + ue_namespace_code ); $bound.unbind( 'touchstart' + ue_namespace_code ); $bound.unbind( 'mousewheel' + ue_namespace_code ); } $.removeData( elem_bound, optionKey ); // Unbind document events only after last element element is removed boundList.remove_val(this); if ( boundList.length === 0 ) { // console.log('last bound element removed - removing global binds'); $(document).unbind( 'mousemove.__ue'); $(document).unbind( 'touchmove.__ue'); $(document).unbind( 'mouseup.__ue'); $(document).unbind( 'touchend.__ue'); isMoveBound = false; } } }; // End define special event handlers //--------------- BEGIN JQUERY SPECIAL EVENTS ---------------- //------------------ BEGIN MOTION CONTROLS ------------------- // Begin motion control /fnHeld/ fnHeld = function ( arg_map ) { var timestamp = +new Date(), motion_id = arg_map.motion_id, motion_map = arg_map.motion_map, bound_ns_map = arg_map.bound_ns_map, event_ue ; delete motion_map.idto_tapheld; if ( ! motion_map.do_allow_tap ) { return; } motion_map.px_end_x = motion_map.px_start_x; motion_map.px_end_y = motion_map.px_start_y; motion_map.ms_timestop = timestamp; motion_map.ms_elapsed = timestamp - motion_map.ms_timestart; if ( bound_ns_map.uheld ) { event_ue = $.Event('uheld'); $.extend( event_ue, motion_map ); $(motion_map.elem_bound).trigger(event_ue); } // remove tracking, as we want no futher action on this motion if ( bound_ns_map.uheldstart ) { event_ue = $.Event('uheldstart'); $.extend( event_ue, motion_map ); $(motion_map.elem_bound).trigger(event_ue); motionHeldId = motion_id; } else { delete motionMapMap[motion_id]; } }; // End motion control /fnHeld/ // Begin motion control /fnMotionStart/ fnMotionStart = function ( arg_map ) { var motion_id = arg_map.motion_id, event_src = arg_map.event_src, request_dzoom = arg_map.request_dzoom, option_map = $.data( arg_map.elem, optionKey ), bound_ns_map = option_map.bound_ns_map, $target = $(event_src.target ), do_zoomstart = false, motion_map, cb_map, do_allow_tap, event_ue ; // this should never happen, but it does if ( motionMapMap[ motion_id ] ) { return; } if ( request_dzoom && ! bound_ns_map.uzoomstart ) { return; } // :input selector includes text areas if ( $target.is( option_map.ignore_class ) ) { return; } do_allow_tap = bound_ns_map.utap || bound_ns_map.uheld || bound_ns_map.uheldstart ? true : false; cb_map = callbackList.pop(); while ( cb_map ) { if ( $target.is( cb_map.selector_str ) || $( arg_map.elem ).is( cb_map.selector_str ) ) { if ( cb_map.callback_match ) { cb_map.callback_match( arg_map ); } } else { if ( cb_map.callback_nomatch ) { cb_map.callback_nomatch( arg_map ); } } cb_map = callbackList.pop(); } motion_map = { do_allow_tap : do_allow_tap, elem_bound : arg_map.elem, elem_target : event_src.target, ms_elapsed : 0, ms_timestart : event_src.timeStamp, ms_timestop : undefined, option_map : option_map, orig_target : event_src.target, px_current_x : event_src.clientX, px_current_y : event_src.clientY, px_end_x : undefined, px_end_y : undefined, px_start_x : event_src.clientX, px_start_y : event_src.clientY, timeStamp : event_src.timeStamp }; motionMapMap[ motion_id ] = motion_map; if ( bound_ns_map.uzoomstart ) { if ( request_dzoom ) { motionDzoomId = motion_id; } else if ( ! motion1ZoomId ) { motion1ZoomId = motion_id; } else if ( ! motion2ZoomId ) { motion2ZoomId = motion_id; event_ue = $.Event('uzoomstart'); do_zoomstart = true; } if ( do_zoomstart ) { event_ue = $.Event( 'uzoomstart' ); motion_map.px_delta_zoom = 0; $.extend( event_ue, motion_map ); $(motion_map.elem_bound).trigger(event_ue); return; } } if ( bound_ns_map.uheld || bound_ns_map.uheldstart ) { motion_map.idto_tapheld = setTimeout( function() { fnHeld({ motion_id : motion_id, motion_map : motion_map, bound_ns_map : bound_ns_map }); }, option_map.held_tap_time ); } }; // End motion control /fnMotionStart/ // Begin motion control /fnMotionMove/ fnMotionMove = function ( arg_map ) { var motion_id = arg_map.motion_id, event_src = arg_map.event_src, do_zoommove = false, motion_map, option_map, bound_ns_map, event_ue, px_pinch_zoom, px_delta_zoom, mzoom1_map, mzoom2_map ; if ( ! motionMapMap[motion_id] ) { return; } motion_map = motionMapMap[motion_id]; option_map = motion_map.option_map; bound_ns_map = option_map.bound_ns_map; motion_map.timeStamp = event_src.timeStamp; motion_map.elem_target = event_src.target; motion_map.ms_elapsed = event_src.timeStamp - motion_map.ms_timestart; motion_map.px_delta_x = event_src.clientX - motion_map.px_current_x; motion_map.px_delta_y = event_src.clientY - motion_map.px_current_y; motion_map.px_current_x = event_src.clientX; motion_map.px_current_y = event_src.clientY; // native event object override motion_map.timeStamp = event_src.timeStamp; // disallow tap if outside of zone or time elapsed // we use this for other events, so we do it every time if ( motion_map.do_allow_tap ) { if ( Math.abs(motion_map.px_delta_x) > option_map.px_radius || Math.abs(motion_map.pd_delta_y) > option_map.px_radius || motion_map.ms_elapsed > option_map.tap_time ) { motion_map.do_allow_tap = false; } } if ( motion1ZoomId && motion2ZoomId && ( motion_id === motion1ZoomId || motion_id === motion2ZoomId )) { motionMapMap[motion_id] = motion_map; mzoom1_map = motionMapMap[motion1ZoomId]; mzoom2_map = motionMapMap[motion2ZoomId]; px_pinch_zoom = Math.floor( Math.sqrt( Math.pow((mzoom1_map.px_current_x - mzoom2_map.px_current_x),2) + Math.pow((mzoom1_map.px_current_y - mzoom2_map.px_current_y),2) ) +0.5 ); if ( pxPinchZoom === -1 ) { px_delta_zoom = 0; } else { px_delta_zoom = ( px_pinch_zoom - pxPinchZoom ) * zoomTouchNum;} // save value for next iteration delta comparison pxPinchZoom = px_pinch_zoom; do_zoommove = true; } else if ( motionDzoomId === motion_id ) { if ( bound_ns_map.uzoommove ) { px_delta_zoom = motion_map.px_delta_y * zoomMouseNum; do_zoommove = true; } } if ( do_zoommove ){ event_ue = $.Event('uzoommove'); motion_map.px_delta_zoom = px_delta_zoom; $.extend( event_ue, motion_map ); $(motion_map.elem_bound).trigger(event_ue); return; } if ( motionHeldId === motion_id ) { if ( bound_ns_map.uheldmove ) { event_ue = $.Event('uheldmove'); $.extend( event_ue, motion_map ); $(motion_map.elem_bound).trigger(event_ue); event_src.preventDefault(); } } else if ( motionDragId === motion_id ) { if ( bound_ns_map.udragmove ) { event_ue = $.Event('udragmove'); $.extend( event_ue, motion_map ); $(motion_map.elem_bound).trigger(event_ue); event_src.preventDefault(); } } if ( ! motionDragId && ! motionHeldId && bound_ns_map.udragstart && motion_map.do_allow_tap === false ) { motionDragId = motion_id; event_ue = $.Event('udragstart'); $.extend( event_ue, motion_map ); $(motion_map.elem_bound).trigger(event_ue); event_src.preventDefault(); if ( motion_map.idto_tapheld ) { clearTimeout(motion_map.idto_tapheld); delete motion_map.idto_tapheld; } } }; // End motion control /fnMotionMove/ // Begin motion control /fnMotionEnd/ fnMotionEnd = function ( arg_map ) { var motion_id = arg_map.motion_id, event_src = arg_map.event_src, do_zoomend = false, motion_map, option_map, bound_ns_map, event_ue ; doDisableMouse = false; if ( ! motionMapMap[motion_id] ) { return; } motion_map = motionMapMap[motion_id]; option_map = motion_map.option_map; bound_ns_map = option_map.bound_ns_map; motion_map.elem_target = event_src.target; motion_map.ms_elapsed = event_src.timeStamp - motion_map.ms_timestart; motion_map.ms_timestop = event_src.timeStamp; if ( motion_map.px_current_x ) { motion_map.px_delta_x = event_src.clientX - motion_map.px_current_x; motion_map.px_delta_y = event_src.clientY - motion_map.px_current_y; } motion_map.px_current_x = event_src.clientX; motion_map.px_current_y = event_src.clientY; motion_map.px_end_x = event_src.clientX; motion_map.px_end_y = event_src.clientY; // native event object override motion_map.timeStamp = event_src.timeStamp ; // clear-out any long-hold tap timer if ( motion_map.idto_tapheld ) { clearTimeout(motion_map.idto_tapheld); delete motion_map.idto_tapheld; } // trigger utap if ( bound_ns_map.utap && motion_map.ms_elapsed <= option_map.tap_time && motion_map.do_allow_tap ) { event_ue = $.Event('utap'); $.extend( event_ue, motion_map ); $(motion_map.elem_bound).trigger(event_ue); } // trigger udragend if ( motion_id === motionDragId ) { if ( bound_ns_map.udragend ) { event_ue = $.Event('udragend'); $.extend( event_ue, motion_map ); $(motion_map.elem_bound).trigger(event_ue); event_src.preventDefault(); } motionDragId = undefined; } // trigger heldend if ( motion_id === motionHeldId ) { if ( bound_ns_map.uheldend ) { event_ue = $.Event('uheldend'); $.extend( event_ue, motion_map ); $(motion_map.elem_bound).trigger(event_ue); } motionHeldId = undefined; } // trigger uzoomend if ( motion_id === motionDzoomId ) { do_zoomend = true; motionDzoomId = undefined; } // cleanup zoom info else if ( motion_id === motion1ZoomId ) { if ( motion2ZoomId ) { motion1ZoomId = motion2ZoomId; motion2ZoomId = undefined; do_zoomend = true; } else { motion1ZoomId = undefined; } pxPinchZoom = -1; } if ( motion_id === motion2ZoomId ) { motion2ZoomId = undefined; pxPinchZoom = -1; do_zoomend = true; } if ( do_zoomend && bound_ns_map.uzoomend ) { event_ue = $.Event('uzoomend'); motion_map.px_delta_zoom = 0; $.extend( event_ue, motion_map ); $(motion_map.elem_bound).trigger(event_ue); } // remove pointer from consideration delete motionMapMap[motion_id]; }; // End motion control /fnMotionEnd/ //------------------ END MOTION CONTROLS ------------------- //------------------- BEGIN EVENT HANDLERS ------------------- // Begin event handler /onTouch/ for all touch events. // We use the 'type' attribute to dispatch to motion control onTouch = function ( event ) { var elem_this = this, timestamp = +new Date(), o_event = event.originalEvent, a_touches = o_event.changedTouches || [], idx, touch_event, motion_id, handler_fn ; doDisableMouse = true; event.timeStamp = timestamp; switch ( event.type ) { case 'touchstart' : handler_fn = fnMotionStart; break; case 'touchmove' : handler_fn = fnMotionMove; break; case 'touchend' : handler_fn = fnMotionEnd; break; default : handler_fn = null; } if ( ! handler_fn ) { return; } for ( idx = 0; idx < a_touches.length; idx++ ) { touch_event = a_touches[idx]; motion_id = 'touch' + String(touch_event.identifier); event.clientX = touch_event.clientX; event.clientY = touch_event.clientY; handler_fn({ elem : elem_this, motion_id : motion_id, event_src : event }); } }; // End event handler /onTouch/ // Begin event handler /onMouse/ for all mouse events // We use the 'type' attribute to dispatch to motion control onMouse = function ( event ) { var elem_this = this, motion_id = 'mouse' + String(event.button), request_dzoom = false, handler_fn ; if ( doDisableMouse ) { event.stopImmediatePropagation(); return; } if ( event.shiftKey ) { request_dzoom = true; } // skip left or middle clicks if ( event.type !== 'mousemove' ) { if ( event.button !== 0 ) { return true; } } switch ( event.type ) { case 'mousedown' : handler_fn = fnMotionStart; event.preventDefault(); break; case 'mouseup' : handler_fn = fnMotionEnd; break; case 'mousemove' : handler_fn = fnMotionMove; event.preventDefault(); break; default : handler_fn = null; } if ( ! handler_fn ) { return; } handler_fn({ elem : elem_this, event_src : event, request_dzoom : request_dzoom, motion_id : motion_id }); }; // End event handler /onMouse/ //-------------------- END EVENT HANDLERS -------------------- // Export special events through jQuery API $Special.ue = $Special.utap = $Special.uheld = $Special.uzoomstart = $Special.uzoommove = $Special.uzoomend = $Special.udragstart = $Special.udragmove = $Special.udragend = $Special.uheldstart = $Special.uheldmove = $Special.uheldend = Ue ; $.ueSetGlobalCb = function ( selector_str, callback_match, callback_nomatch ) { callbackList.push( { selector_str : selector_str || '', callback_match : callback_match || null, callback_nomatch : callback_nomatch || null }); }; }(jQuery));