以下是 html5 svg手机播放器按钮动画特效代码 的示例演示效果:
部分效果截图:
HTML代码(index.html):
<!doctype html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>html5 svg手机播放器按钮动画特效</title>
<link rel="stylesheet" type="text/css" href="css/normalize.css" />
<link rel="stylesheet" type="text/css" href="css/default.css">
<link rel="stylesheet" type="text/css" href="css/styles.css">
</head>
<body>
<div class="view__menu">
<div class="menu__inner">
<div class="btn btn--play-pause">
<svg viewbox="0 0 40 40" class="btn__icon">
<circle cx="20" cy="20" r="19" transform="rotate(-90 20 20)" class="icon__shape icon__shape--circle icon__shape--orange"></circle>
<circle cx="20" cy="20" r="19" transform="rotate(-90 20 20)" class="icon__shape icon__shape--circle icon__shape--white"></circle>
<polygon points="16,14 16,26 27.5,19.8" class="icon__shape icon__shape--triangle"></polygon>
<line x1="24" y1="14" x2="24" y2="26" class="icon__shape icon__shape--line"></line>
</svg>
<div class="btn__shadow"></div>
</div>
<div class="btn btn--volume btn--volume-2">
<svg viewbox="0 0 174 40" class="btn__icon">
<path d="M2.8,20.4c0-17.4,19-19,19-19v38C21.8,39.4,2.8,37.8,2.8,20.4z" class="icon__shape"></path>
<line x1="26" y1="7.9" x2="1" y2="32.9" class="icon__shape icon__shape--line-mute icon__shape--orange"></line>
<line x1="26" y1="7.9" x2="1" y2="32.9" class="icon__shape icon__shape--line-mute icon__shape--white"></line>
<line x1="37.8" y1="20" x2="162.8" y2="20" class="icon__shape icon__shape--line-controls icon__shape--translucide"></line>
<line x1="37.8" y1="20" x2="162.8" y2="20" class="icon__shape icon__shape--line-controls icon__shape--white"></line>
<circle cx="24.8" cy="20" r="19" transform="rotate(45 25 20)" class="icon__shape icon__shape--circle-big icon__shape--translucide"></circle>
<circle cx="24.8" cy="20" r="13" transform="rotate(-45 25 20)" class="icon__shape icon__shape--circle-medium icon__shape--translucide"></circle>
<circle cx="24.8" cy="20" r="7" transform="rotate(-45 25 20)" class="icon__shape icon__shape--circle-small icon__shape--translucide"></circle>
<circle cx="37.8" cy="20" r="0" class="icon__shape icon__shape--circle-controls"></circle>
<circle cx="37.8" cy="20" r="1" class="icon__shape icon__shape--circle-placeholder"></circle>
</svg>
<div class="btn__shadow"></div>
</div>
<div class="menu__credits"></div>
</div>
</div>
<script type="text/javascript" src='js/jquery-2.1.0.min.js'></script>
<script type="text/javascript" src='js/hammer.js'></script>
<script type="text/javascript" src='js/TweenMax.min.js'></script>
<script type="text/javascript" src="js/main.js"></script>
</body>
</html>
JS代码(hammer.js):
/*! Hammer.JS - v2.0.4 - 2014-09-28 * http://hammerjs.github.io/ * * Copyright (c) 2014 Jorik Tangelder;
* Licensed under the MIT license */
(function(window,document,exportName,undefined){
'use strict';
var VENDOR_PREFIXES = ['','webkit','moz','MS','ms','o'];
var TEST_ELEMENT = document.createElement('div');
var TYPE_FUNCTION = 'function';
var round = Math.round;
var abs = Math.abs;
var now = Date.now;
/** * set a timeout with a given scope * @param{
Function}
fn * @param{
Number}
timeout * @param{
Object}
context * @returns{
number}
*/
function setTimeoutContext(fn,timeout,context){
return setTimeout(bindFn(fn,context),timeout);
}
/** * if the argument is an array,we want to execute the fn on each entry * if it aint an array we don't want to do a thing. * this is used by all the methods that accept a single and array argument. * @param{
*|Array}
arg * @param{
String}
fn * @param{
Object}
[context] * @returns{
Boolean}
*/
function invokeArrayArg(arg,fn,context){
if (Array.isArray(arg)){
each(arg,context[fn],context);
return true;
}
return false;
}
/** * walk objects and arrays * @param{
Object}
obj * @param{
Function}
iterator * @param{
Object}
context */
function each(obj,iterator,context){
var i;
if (!obj){
return;
}
if (obj.forEach){
obj.forEach(iterator,context);
}
else if (obj.length !== undefined){
i = 0;
while (i < obj.length){
iterator.call(context,obj[i],i,obj);
i++;
}
}
else{
for (i in obj){
obj.hasOwnProperty(i) && iterator.call(context,obj[i],i,obj);
}
}
}
/** * extend object. * means that properties in dest will be overwritten by the ones in src. * @param{
Object}
dest * @param{
Object}
src * @param{
Boolean}
[merge] * @returns{
Object}
dest */
function extend(dest,src,merge){
var keys = Object.keys(src);
var i = 0;
while (i < keys.length){
if (!merge || (merge && dest[keys[i]] === undefined)){
dest[keys[i]] = src[keys[i]];
}
i++;
}
return dest;
}
/** * merge the values from src in the dest. * means that properties that exist in dest will not be overwritten by src * @param{
Object}
dest * @param{
Object}
src * @returns{
Object}
dest */
function merge(dest,src){
return extend(dest,src,true);
}
/** * simple class inheritance * @param{
Function}
child * @param{
Function}
base * @param{
Object}
[properties] */
function inherit(child,base,properties){
var baseP = base.prototype,childP;
childP = child.prototype = Object.create(baseP);
childP.constructor = child;
childP._super = baseP;
if (properties){
extend(childP,properties);
}
}
/** * simple function bind * @param{
Function}
fn * @param{
Object}
context * @returns{
Function}
*/
function bindFn(fn,context){
return function boundFn(){
return fn.apply(context,arguments);
}
;
}
/** * let a boolean value also be a function that must return a boolean * this first item in args will be used as the context * @param{
Boolean|Function}
val * @param{
Array}
[args] * @returns{
Boolean}
*/
function boolOrFn(val,args){
if (typeof val == TYPE_FUNCTION){
return val.apply(args ? args[0] || undefined:undefined,args);
}
return val;
}
/** * use the val2 when val1 is undefined * @param{
*}
val1 * @param{
*}
val2 * @returns{
*}
*/
function ifUndefined(val1,val2){
return (val1 === undefined) ? val2:val1;
}
/** * addEventListener with multiple events at once * @param{
EventTarget}
target * @param{
String}
types * @param{
Function}
handler */
function addEventListeners(target,types,handler){
each(splitStr(types),function(type){
target.addEventListener(type,handler,false);
}
);
}
/** * removeEventListener with multiple events at once * @param{
EventTarget}
target * @param{
String}
types * @param{
Function}
handler */
function removeEventListeners(target,types,handler){
each(splitStr(types),function(type){
target.removeEventListener(type,handler,false);
}
);
}
/** * find if a node is in the given parent * @method hasParent * @param{
HTMLElement}
node * @param{
HTMLElement}
parent * @return{
Boolean}
found */
function hasParent(node,parent){
while (node){
if (node == parent){
return true;
}
node = node.parentNode;
}
return false;
}
/** * small indexOf wrapper * @param{
String}
str * @param{
String}
find * @returns{
Boolean}
found */
function inStr(str,find){
return str.indexOf(find) > -1;
}
/** * split string on whitespace * @param{
String}
str * @returns{
Array}
words */
function splitStr(str){
return str.trim().split(/\s+/g);
}
/** * find if a array contains the object using indexOf or a simple polyFill * @param{
Array}
src * @param{
String}
find * @param{
String}
[findByKey] * @return{
Boolean|Number}
false when not found,or the index */
function inArray(src,find,findByKey){
if (src.indexOf && !findByKey){
return src.indexOf(find);
}
else{
var i = 0;
while (i < src.length){
if ((findByKey && src[i][findByKey] == find) || (!findByKey && src[i] === find)){
return i;
}
i++;
}
return -1;
}
}
/** * convert array-like objects to real arrays * @param{
Object}
obj * @returns{
Array}
*/
function toArray(obj){
return Array.prototype.slice.call(obj,0);
}
/** * unique array with objects based on a key (like 'id') or just by the array's value * @param{
Array}
src [{
id:1}
,{
id:2}
,{
id:1}
] * @param{
String}
[key] * @param{
Boolean}
[sort=False] * @returns{
Array}
[{
id:1}
,{
id:2}
] */
function uniqueArray(src,key,sort){
var results = [];
var values = [];
var i = 0;
while (i < src.length){
var val = key ? src[i][key]:src[i];
if (inArray(values,val) < 0){
results.push(src[i]);
}
values[i] = val;
i++;
}
if (sort){
if (!key){
results = results.sort();
}
else{
results = results.sort(function sortUniqueArray(a,b){
return a[key] > b[key];
}
);
}
}
return results;
}
/** * get the prefixed property * @param{
Object}
obj * @param{
String}
property * @returns{
String|Undefined}
prefixed */
function prefixed(obj,property){
var prefix,prop;
var camelProp = property[0].toUpperCase() + property.slice(1);
var i = 0;
while (i < VENDOR_PREFIXES.length){
prefix = VENDOR_PREFIXES[i];
prop = (prefix) ? prefix + camelProp:property;
if (prop in obj){
return prop;
}
i++;
}
return undefined;
}
/** * get a unique id * @returns{
number}
uniqueId */
var _uniqueId = 1;
function uniqueId(){
return _uniqueId++;
}
/** * get the window object of an element * @param{
HTMLElement}
element * @returns{
DocumentView|Window}
*/
function getWindowForElement(element){
var doc = element.ownerDocument;
return (doc.defaultView || doc.parentWindow);
}
var MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i;
var SUPPORT_TOUCH = ('ontouchstart' in window);
var SUPPORT_POINTER_EVENTS = prefixed(window,'PointerEvent') !== undefined;
var SUPPORT_ONLY_TOUCH = SUPPORT_TOUCH && MOBILE_REGEX.test(navigator.userAgent);
var INPUT_TYPE_TOUCH = 'touch';
var INPUT_TYPE_PEN = 'pen';
var INPUT_TYPE_MOUSE = 'mouse';
var INPUT_TYPE_KINECT = 'kinect';
var COMPUTE_INTERVAL = 25;
var INPUT_START = 1;
var INPUT_MOVE = 2;
var INPUT_END = 4;
var INPUT_CANCEL = 8;
var DIRECTION_NONE = 1;
var DIRECTION_LEFT = 2;
var DIRECTION_RIGHT = 4;
var DIRECTION_UP = 8;
var DIRECTION_DOWN = 16;
var DIRECTION_HORIZONTAL = DIRECTION_LEFT | DIRECTION_RIGHT;
var DIRECTION_VERTICAL = DIRECTION_UP | DIRECTION_DOWN;
var DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL;
var PROPS_XY = ['x','y'];
var PROPS_CLIENT_XY = ['clientX','clientY'];
/** * create new input type manager * @param{
Manager}
manager * @param{
Function}
callback * @returns{
Input}
* @constructor */
function Input(manager,callback){
var self = this;
this.manager = manager;
this.callback = callback;
this.element = manager.element;
this.target = manager.options.inputTarget;
// smaller wrapper around the handler,for the scope and the enabled state of the manager,// so when disabled the input events are completely bypassed. this.domHandler = function(ev){
if (boolOrFn(manager.options.enable,[manager])){
self.handler(ev);
}
}
;
this.init();
}
Input.prototype ={
/** * should handle the inputEvent data and trigger the callback * @virtual */
handler:function(){
}
,/** * bind the events */
init:function(){
this.evEl && addEventListeners(this.element,this.evEl,this.domHandler);
this.evTarget && addEventListeners(this.target,this.evTarget,this.domHandler);
this.evWin && addEventListeners(getWindowForElement(this.element),this.evWin,this.domHandler);
}
,/** * unbind the events */
destroy:function(){
this.evEl && removeEventListeners(this.element,this.evEl,this.domHandler);
this.evTarget && removeEventListeners(this.target,this.evTarget,this.domHandler);
this.evWin && removeEventListeners(getWindowForElement(this.element),this.evWin,this.domHandler);
}
}
;
/** * create new input type manager * called by the Manager constructor * @param{
Hammer}
manager * @returns{
Input}
*/
function createInputInstance(manager){
var Type;
var inputClass = manager.options.inputClass;
if (inputClass){
Type = inputClass;
}
else if (SUPPORT_POINTER_EVENTS){
Type = PointerEventInput;
}
else if (SUPPORT_ONLY_TOUCH){
Type = TouchInput;
}
else if (!SUPPORT_TOUCH){
Type = MouseInput;
}
else{
Type = TouchMouseInput;
}
return new (Type)(manager,inputHandler);
}
/** * handle input events * @param{
Manager}
manager * @param{
String}
eventType * @param{
Object}
input */
function inputHandler(manager,eventType,input){
var pointersLen = input.pointers.length;
var changedPointersLen = input.changedPointers.length;
var isFirst = (eventType & INPUT_START && (pointersLen - changedPointersLen === 0));
var isFinal = (eventType & (INPUT_END | INPUT_CANCEL) && (pointersLen - changedPointersLen === 0));
input.isFirst = !!isFirst;
input.isFinal = !!isFinal;
if (isFirst){
manager.session ={
}
;
}
// source event is the normalized value of the domEvents // like 'touchstart,mouseup,pointerdown' input.eventType = eventType;
// compute scale,rotation etc computeInputData(manager,input);
// emit secret event manager.emit('hammer.input',input);
manager.recognize(input);
manager.session.prevInput = input;
}
/** * extend the data with some usable properties like scale,rotate,velocity etc * @param{
Object}
manager * @param{
Object}
input */
function computeInputData(manager,input){
var session = manager.session;
var pointers = input.pointers;
var pointersLength = pointers.length;
// store the first input to calculate the distance and direction if (!session.firstInput){
session.firstInput = simpleCloneInputData(input);
}
// to compute scale and rotation we need to store the multiple touches if (pointersLength > 1 && !session.firstMultiple){
session.firstMultiple = simpleCloneInputData(input);
}
else if (pointersLength === 1){
session.firstMultiple = false;
}
var firstInput = session.firstInput;
var firstMultiple = session.firstMultiple;
var offsetCenter = firstMultiple ? firstMultiple.center:firstInput.center;
var center = input.center = getCenter(pointers);
input.timeStamp = now();
input.deltaTime = input.timeStamp - firstInput.timeStamp;
input.angle = getAngle(offsetCenter,center);
input.distance = getDistance(offsetCenter,center);
computeDeltaXY(session,input);
input.offsetDirection = getDirection(input.deltaX,input.deltaY);
input.scale = firstMultiple ? getScale(firstMultiple.pointers,pointers):1;
input.rotation = firstMultiple ? getRotation(firstMultiple.pointers,pointers):0;
computeIntervalInputData(session,input);
// find the correct target var target = manager.element;
if (hasParent(input.srcEvent.target,target)){
target = input.srcEvent.target;
}
input.target = target;
}
function computeDeltaXY(session,input){
var center = input.center;
var offset = session.offsetDelta ||{
}
;
var prevDelta = session.prevDelta ||{
}
;
var prevInput = session.prevInput ||{
}
;
if (input.eventType === INPUT_START || prevInput.eventType === INPUT_END){
prevDelta = session.prevDelta ={
x:prevInput.deltaX || 0,y:prevInput.deltaY || 0}
;
offset = session.offsetDelta ={
x:center.x,y:center.y}
;
}
input.deltaX = prevDelta.x + (center.x - offset.x);
input.deltaY = prevDelta.y + (center.y - offset.y);
}
/** * velocity is calculated every x ms * @param{
Object}
session * @param{
Object}
input */
function computeIntervalInputData(session,input){
var last = session.lastInterval || input,deltaTime = input.timeStamp - last.timeStamp,velocity,velocityX,velocityY,direction;
if (input.eventType != INPUT_CANCEL && (deltaTime > COMPUTE_INTERVAL || last.velocity === undefined)){
var deltaX = last.deltaX - input.deltaX;
var deltaY = last.deltaY - input.deltaY;
var v = getVelocity(deltaTime,deltaX,deltaY);
velocityX = v.x;
velocityY = v.y;
velocity = (abs(v.x) > abs(v.y)) ? v.x:v.y;
direction = getDirection(deltaX,deltaY);
session.lastInterval = input;
}
else{
// use latest velocity info if it doesn't overtake a minimum period velocity = last.velocity;
velocityX = last.velocityX;
velocityY = last.velocityY;
direction = last.direction;
}
input.velocity = velocity;
input.velocityX = velocityX;
input.velocityY = velocityY;
input.direction = direction;
}
/** * create a simple clone from the input used for storage of firstInput and firstMultiple * @param{
Object}
input * @returns{
Object}
clonedInputData */
function simpleCloneInputData(input){
// make a simple copy of the pointers because we will get a reference if we don't // we only need clientXY for the calculations var pointers = [];
var i = 0;
while (i < input.pointers.length){
pointers[i] ={
clientX:round(input.pointers[i].clientX),clientY:round(input.pointers[i].clientY)}
;
i++;
}
return{
timeStamp:now(),pointers:pointers,center:getCenter(pointers),deltaX:input.deltaX,deltaY:input.deltaY}
;
}
/** * get the center of all the pointers * @param{
Array}
pointers * @return{
Object}
center contains `x` and `y` properties */
function getCenter(pointers){
var pointersLength = pointers.length;
// no need to loop when only one touch if (pointersLength === 1){
return{
x:round(pointers[0].clientX),y:round(pointers[0].clientY)}
;
}
var x = 0,y = 0,i = 0;
while (i < pointersLength){
x += pointers[i].clientX;
y += pointers[i].clientY;
i++;
}
return{
x:round(x / pointersLength),y:round(y / pointersLength)}
;
}
/** * calculate the velocity between two points. unit is in px per ms. * @param{
Number}
deltaTime * @param{
Number}
x * @param{
Number}
y * @return{
Object}
velocity `x` and `y` */
function getVelocity(deltaTime,x,y){
return{
x:x / deltaTime || 0,y:y / deltaTime || 0}
;
}
/** * get the direction between two points * @param{
Number}
x * @param{
Number}
y * @return{
Number}
direction */
function getDirection(x,y){
if (x === y){
return DIRECTION_NONE;
}
if (abs(x) >= abs(y)){
return x > 0 ? DIRECTION_LEFT:DIRECTION_RIGHT;
}
return y > 0 ? DIRECTION_UP:DIRECTION_DOWN;
}
/** * calculate the absolute distance between two points * @param{
Object}
p1{
x,y}
* @param{
Object}
p2{
x,y}
* @param{
Array}
[props] containing x and y keys * @return{
Number}
distance */
function getDistance(p1,p2,props){
if (!props){
props = PROPS_XY;
}
var x = p2[props[0]] - p1[props[0]],y = p2[props[1]] - p1[props[1]];
return Math.sqrt((x * x) + (y * y));
}
/** * calculate the angle between two coordinates * @param{
Object}
p1 * @param{
Object}
p2 * @param{
Array}
[props] containing x and y keys * @return{
Number}
angle */
function getAngle(p1,p2,props){
if (!props){
props = PROPS_XY;
}
var x = p2[props[0]] - p1[props[0]],y = p2[props[1]] - p1[props[1]];
return Math.atan2(y,x) * 180 / Math.PI;
}
/** * calculate the rotation degrees between two pointersets * @param{
Array}
start array of pointers * @param{
Array}
end array of pointers * @return{
Number}
rotation */
function getRotation(start,end){
return getAngle(end[1],end[0],PROPS_CLIENT_XY) - getAngle(start[1],start[0],PROPS_CLIENT_XY);
}
/** * calculate the scale factor between two pointersets * no scale is 1,and goes down to 0 when pinched together,and bigger when pinched out * @param{
Array}
start array of pointers * @param{
Array}
end array of pointers * @return{
Number}
scale */
function getScale(start,end){
return getDistance(end[0],end[1],PROPS_CLIENT_XY) / getDistance(start[0],start[1],PROPS_CLIENT_XY);
}
var MOUSE_INPUT_MAP ={
mousedown:INPUT_START,mousemove:INPUT_MOVE,mouseup:INPUT_END}
;
var MOUSE_ELEMENT_EVENTS = 'mousedown';
var MOUSE_WINDOW_EVENTS = 'mousemove mouseup';
/** * Mouse events input * @constructor * @extends Input */
function MouseInput(){
this.evEl = MOUSE_ELEMENT_EVENTS;
this.evWin = MOUSE_WINDOW_EVENTS;
this.allow = true;
// used by Input.TouchMouse to disable mouse events this.pressed = false;
// mousedown state Input.apply(this,arguments);
}
inherit(MouseInput,Input,{
/** * handle mouse events * @param{
Object}
ev */
handler:function MEhandler(ev){
var eventType = MOUSE_INPUT_MAP[ev.type];
// on start we want to have the left mouse button down if (eventType & INPUT_START && ev.button === 0){
this.pressed = true;
}
if (eventType & INPUT_MOVE && ev.which !== 1){
eventType = INPUT_END;
}
// mouse must be down,and mouse events are allowed (see the TouchMouse input) if (!this.pressed || !this.allow){
return;
}
if (eventType & INPUT_END){
this.pressed = false;
}
this.callback(this.manager,eventType,{
pointers:[ev],changedPointers:[ev],pointerType:INPUT_TYPE_MOUSE,srcEvent:ev}
);
}
}
);
var POINTER_INPUT_MAP ={
pointerdown:INPUT_START,pointermove:INPUT_MOVE,pointerup:INPUT_END,pointercancel:INPUT_CANCEL,pointerout:INPUT_CANCEL}
;
// in IE10 the pointer types is defined as an enumvar IE10_POINTER_TYPE_ENUM ={
2:INPUT_TYPE_TOUCH,3:INPUT_TYPE_PEN,4:INPUT_TYPE_MOUSE,5:INPUT_TYPE_KINECT // see https://twitter.com/jacobrossi/status/480596438489890816}
;
var POINTER_ELEMENT_EVENTS = 'pointerdown';
var POINTER_WINDOW_EVENTS = 'pointermove pointerup pointercancel';
// IE10 has prefixed support,and case-sensitiveif (window.MSPointerEvent){
POINTER_ELEMENT_EVENTS = 'MSPointerDown';
POINTER_WINDOW_EVENTS = 'MSPointerMove MSPointerUp MSPointerCancel';
}
/** * Pointer events input * @constructor * @extends Input */
function PointerEventInput(){
this.evEl = POINTER_ELEMENT_EVENTS;
this.evWin = POINTER_WINDOW_EVENTS;
Input.apply(this,arguments);
this.store = (this.manager.session.pointerEvents = []);
}
inherit(PointerEventInput,Input,{
/** * handle mouse events * @param{
Object}
ev */
handler:function PEhandler(ev){
var store = this.store;
var removePointer = false;
var eventTypeNormalized = ev.type.toLowerCase().replace('ms','');
var eventType = POINTER_INPUT_MAP[eventTypeNormalized];
var pointerType = IE10_POINTER_TYPE_ENUM[ev.pointerType] || ev.pointerType;
var isTouch = (pointerType == INPUT_TYPE_TOUCH);
// get index of the event in the store var storeIndex = inArray(store,ev.pointerId,'pointerId');
// start and mouse must be down if (eventType & INPUT_START && (ev.button === 0 || isTouch)){
if (storeIndex < 0){
store.push(ev);
storeIndex = store.length - 1;
}
}
else if (eventType & (INPUT_END | INPUT_CANCEL)){
removePointer = true;
}
// it not found,so the pointer hasn't been down (so it's probably a hover) if (storeIndex < 0){
return;
}
// update the event in the store store[storeIndex] = ev;
this.callback(this.manager,eventType,{
pointers:store,changedPointers:[ev],pointerType:pointerType,srcEvent:ev}
);
if (removePointer){
// remove from the store store.splice(storeIndex,1);
}
}
}
);
var SINGLE_TOUCH_INPUT_MAP ={
touchstart:INPUT_START,touchmove:INPUT_MOVE,touchend:INPUT_END,touchcancel:INPUT_CANCEL}
;
var SINGLE_TOUCH_TARGET_EVENTS = 'touchstart';
var SINGLE_TOUCH_WINDOW_EVENTS = 'touchstart touchmove touchend touchcancel';
/** * Touch events input * @constructor * @extends Input */
function SingleTouchInput(){
this.evTarget = SINGLE_TOUCH_TARGET_EVENTS;
this.evWin = SINGLE_TOUCH_WINDOW_EVENTS;
this.started = false;
Input.apply(this,arguments);
}
inherit(SingleTouchInput,Input,{
handler:function TEhandler(ev){
var type = SINGLE_TOUCH_INPUT_MAP[ev.type];
// should we handle the touch events? if (type === INPUT_START){
this.started = true;
}
if (!this.started){
return;
}
var touches = normalizeSingleTouches.call(this,ev,type);
// when done,reset the started state if (type & (INPUT_END | INPUT_CANCEL) && touches[0].length - touches[1].length === 0){
this.started = false;
}
this.callback(this.manager,type,{
pointers:touches[0],changedPointers:touches[1],pointerType:INPUT_TYPE_TOUCH,srcEvent:ev}
);
}
}
);
/** * @this{
TouchInput}
* @param{
Object}
ev * @param{
Number}
type flag * @returns{
undefined|Array}
[all,changed] */
function normalizeSingleTouches(ev,type){
var all = toArray(ev.touches);
var changed = toArray(ev.changedTouches);
if (type & (INPUT_END | INPUT_CANCEL)){
all = uniqueArray(all.concat(changed),'identifier',true);
}
return [all,changed];
}
var TOUCH_INPUT_MAP ={
touchstart:INPUT_START,touchmove:INPUT_MOVE,touchend:INPUT_END,touchcancel:INPUT_CANCEL}
;
var TOUCH_TARGET_EVENTS = 'touchstart touchmove touchend touchcancel';
/** * Multi-user touch events input * @constructor * @extends Input */
function TouchInput(){
this.evTarget = TOUCH_TARGET_EVENTS;
this.targetIds ={
}
;
Input.apply(this,arguments);
}
inherit(TouchInput,Input,{
handler:function MTEhandler(ev){
var type = TOUCH_INPUT_MAP[ev.type];
var touches = getTouches.call(this,ev,type);
if (!touches){
return;
}
this.callback(this.manager,type,{
pointers:touches[0],changedPointers:touches[1],pointerType:INPUT_TYPE_TOUCH,srcEvent:ev}
);
}
}
);
/** * @this{
TouchInput}
* @param{
Object}
ev * @param{
Number}
type flag * @returns{
undefined|Array}
[all,changed] */
function getTouches(ev,type){
var allTouches = toArray(ev.touches);
var targetIds = this.targetIds;
// when there is only one touch,the process can be simplified if (type & (INPUT_START | INPUT_MOVE) && allTouches.length === 1){
targetIds[allTouches[0].identifier] = true;
return [allTouches,allTouches];
}
var i,targetTouches,changedTouches = toArray(ev.changedTouches),changedTargetTouches = [],target = this.target;
// get target touches from touches targetTouches = allTouches.filter(function(touch){
return hasParent(touch.target,target);
}
);
// collect touches if (type === INPUT_START){
i = 0;
while (i < targetTouches.length){
targetIds[targetTouches[i].identifier] = true;
i++;
}
}
// filter changed touches to only contain touches that exist in the collected target ids i = 0;
while (i < changedTouches.length){
if (targetIds[changedTouches[i].identifier]){
changedTargetTouches.push(changedTouches[i]);
}
// cleanup removed touches if (type & (INPUT_END | INPUT_CANCEL)){
delete targetIds[changedTouches[i].identifier];
}
i++;
}
if (!changedTargetTouches.length){
return;
}
return [ // merge targetTouches with changedTargetTouches so it contains ALL touches,including 'end' and 'cancel' uniqueArray(targetTouches.concat(changedTargetTouches),'identifier',true),changedTargetTouches ];
}
/** * Combined touch and mouse input * * Touch has a higher priority then mouse,and while touching no mouse events are allowed. * This because touch devices also emit mouse events while doing a touch. * * @constructor * @extends Input */
function TouchMouseInput(){
Input.apply(this,arguments);
var handler = bindFn(this.handler,this);
this.touch = new TouchInput(this.manager,handler);
this.mouse = new MouseInput(this.manager,handler);
}
inherit(TouchMouseInput,Input,{
/** * handle mouse and touch events * @param{
Hammer}
manager * @param{
String}
inputEvent * @param{
Object}
inputData */
handler:function TMEhandler(manager,inputEvent,inputData){
var isTouch = (inputData.pointerType == INPUT_TYPE_TOUCH),isMouse = (inputData.pointerType == INPUT_TYPE_MOUSE);
// when we're in a touch event,so block all upcoming mouse events // most mobile browser also emit mouseevents,right after touchstart if (isTouch){
this.mouse.allow = false;
}
else if (isMouse && !this.mouse.allow){
return;
}
// reset the allowMouse when we're done if (inputEvent & (INPUT_END | INPUT_CANCEL)){
this.mouse.allow = true;
}
this.callback(manager,inputEvent,inputData);
}
,/** * remove the event listeners */
destroy:function destroy(){
this.touch.destroy();
this.mouse.destroy();
}
}
);
var PREFIXED_TOUCH_ACTION = prefixed(TEST_ELEMENT.style,'touchAction');
var NATIVE_TOUCH_ACTION = PREFIXED_TOUCH_ACTION !== undefined;
// magical touchAction valuevar TOUCH_ACTION_COMPUTE = 'compute';
var TOUCH_ACTION_AUTO = 'auto';
var TOUCH_ACTION_MANIPULATION = 'manipulation';
// not implementedvar TOUCH_ACTION_NONE = 'none';
var TOUCH_ACTION_PAN_X = 'pan-x';
var TOUCH_ACTION_PAN_Y = 'pan-y';
/** * Touch Action * sets the touchAction property or uses the js alternative * @param{
Manager}
manager * @param{
String}
value * @constructor */
function TouchAction(manager,value){
this.manager = manager;
this.set(value);
}
TouchAction.prototype ={
/** * set the touchAction value on the element or enable the polyfill * @param{
String}
value */
set:function(value){
// find out the touch-action by the event handlers if (value == TOUCH_ACTION_COMPUTE){
value = this.compute();
}
if (NATIVE_TOUCH_ACTION){
this.manager.element.style[PREFIXED_TOUCH_ACTION] = value;
}
this.actions = value.toLowerCase().trim();
}
,/** * just re-set the touchAction value */
update:function(){
this.set(this.manager.options.touchAction);
}
,/** * compute the value for the touchAction property based on the recognizer's settings * @returns{
String}
value */
compute:function(){
var actions = [];
each(this.manager.recognizers,function(recognizer){
if (boolOrFn(recognizer.options.enable,[recognizer])){
actions = actions.concat(recognizer.getTouchAction());
}
}
);
return cleanTouchActions(actions.join(' '));
}
,/** * this method is called on each input cycle and provides the preventing of the browser behavior * @param{
Object}
input */
preventDefaults:function(input){
// not needed with native support for the touchAction property if (NATIVE_TOUCH_ACTION){
return;
}
var srcEvent = input.srcEvent;
var direction = input.offsetDirection;
// if the touch action did prevented once this session if (this.manager.session.prevented){
srcEvent.preventDefault();
return;
}
var actions = this.actions;
var hasNone = inStr(actions,TOUCH_ACTION_NONE);
var hasPanY = inStr(actions,TOUCH_ACTION_PAN_Y);
var hasPanX = inStr(actions,TOUCH_ACTION_PAN_X);
if (hasNone || (hasPanY && direction & DIRECTION_HORIZONTAL) || (hasPanX && direction & DIRECTION_VERTICAL)){
return this.preventSrc(srcEvent);
}
}
,/** * call preventDefault to prevent the browser's default behavior (scrolling in most cases) * @param{
Object}
srcEvent */
preventSrc:function(srcEvent){
this.manager.session.prevented = true;
srcEvent.preventDefault();
}
}
;
/** * when the touchActions are collected they are not a valid value,so we need to clean things up. * * @param{
String}
actions * @returns{
*}
*/
function cleanTouchActions(actions){
// none if (inStr(actions,TOUCH_ACTION_NONE)){
return TOUCH_ACTION_NONE;
}
var hasPanX = inStr(actions,TOUCH_ACTION_PAN_X);
var hasPanY = inStr(actions,TOUCH_ACTION_PAN_Y);
// pan-x and pan-y can be combined if (hasPanX && hasPanY){
return TOUCH_ACTION_PAN_X + ' ' + TOUCH_ACTION_PAN_Y;
}
// pan-x OR pan-y if (hasPanX || hasPanY){
return hasPanX ? TOUCH_ACTION_PAN_X:TOUCH_ACTION_PAN_Y;
}
// manipulation if (inStr(actions,TOUCH_ACTION_MANIPULATION)){
return TOUCH_ACTION_MANIPULATION;
}
return TOUCH_ACTION_AUTO;
}
/** * Recognizer flow explained;
* * All recognizers have the initial state of POSSIBLE when a input session starts. * The definition of a input session is from the first input until the last input,with all it's movement in it. * * Example session for mouse-input:mousedown -> mousemove -> mouseup * * On each recognizing cycle (see Manager.recognize) the .recognize() method is executed * which determines with state it should be. * * If the recognizer has the state FAILED,CANCELLED or RECOGNIZED (equals ENDED),it is reset to * POSSIBLE to give it another change on the next cycle. * * Possible * | * +-----+---------------+ * | | * +-----+-----+ | * | | | * Failed Cancelled | * +-------+------+ * | | * Recognized Began * | * Changed * | * Ended/Recognized */
var STATE_POSSIBLE = 1;
var STATE_BEGAN = 2;
var STATE_CHANGED = 4;
var STATE_ENDED = 8;
var STATE_RECOGNIZED = STATE_ENDED;
var STATE_CANCELLED = 16;
var STATE_FAILED = 32;
/** * Recognizer * Every recognizer needs to extend from this class. * @constructor * @param{
Object}
options */
function Recognizer(options){
this.id = uniqueId();
this.manager = null;
this.options = merge(options ||{
}
,this.defaults);
// default is enable true this.options.enable = ifUndefined(this.options.enable,true);
this.state = STATE_POSSIBLE;
this.simultaneous ={
}
;
this.requireFail = [];
}
Recognizer.prototype ={
/** * @virtual * @type{
Object}
*/
defaults:{
}
,/** * set options * @param{
Object}
options * @return{
Recognizer}
*/
set:function(options){
extend(this.options,options);
// also update the touchAction,in case something changed about the directions/enabled state this.manager && this.manager.touchAction.update();
return this;
}
,/** * recognize simultaneous with an other recognizer. * @param{
Recognizer}
otherRecognizer * @returns{
Recognizer}
this */
recognizeWith:function(otherRecognizer){
if (invokeArrayArg(otherRecognizer,'recognizeWith',this)){
return this;
}
var simultaneous = this.simultaneous;
otherRecognizer = getRecognizerByNameIfManager(otherRecognizer,this);
if (!simultaneous[otherRecognizer.id]){
simultaneous[otherRecognizer.id] = otherRecognizer;
otherRecognizer.recognizeWith(this);
}
return this;
}
,/** * drop the simultaneous link. it doesnt remove the link on the other recognizer. * @param{
Recognizer}
otherRecognizer * @returns{
Recognizer}
this */
dropRecognizeWith:function(otherRecognizer){
if (invokeArrayArg(otherRecognizer,'dropRecognizeWith',this)){
return this;
}
otherRecognizer = getRecognizerByNameIfManager(otherRecognizer,this);
delete this.simultaneous[otherRecognizer.id];
return this;
}
,/** * recognizer can only run when an other is failing * @param{
Recognizer}
otherRecognizer * @returns{
Recognizer}
this */
requireFailure:function(otherRecognizer){
if (invokeArrayArg(otherRecognizer,'requireFailure',this)){
return this;
}
var requireFail = this.requireFail;
otherRecognizer = getRecognizerByNameIfManager(otherRecognizer,this);
if (inArray(requireFail,otherRecognizer) === -1){
requireFail.push(otherRecognizer);
otherRecognizer.requireFailure(this);
}
return this;
}
,/** * drop the requireFailure link. it does not remove the link on the other recognizer. * @param{
Recognizer}
otherRecognizer * @returns{
Recognizer}
this */
dropRequireFailure:function(otherRecognizer){
if (invokeArrayArg(otherRecognizer,'dropRequireFailure',this)){
return this;
}
otherRecognizer = getRecognizerByNameIfManager(otherRecognizer,this);
var index = inArray(this.requireFail,otherRecognizer);
if (index > -1){
this.requireFail.splice(index,1);
}
return this;
}
,/** * has require failures boolean * @returns{
boolean}
*/
hasRequireFailures:function(){
return this.requireFail.length > 0;
}
,/** * if the recognizer can recognize simultaneous with an other recognizer * @param{
Recognizer}
otherRecognizer * @returns{
Boolean}
*/
canRecognizeWith:function(otherRecognizer){
return !!this.simultaneous[otherRecognizer.id];
}
,/** * You should use `tryEmit` instead of `emit` directly to check * that all the needed recognizers has failed before emitting. * @param{
Object}
input */
emit:function(input){
var self = this;
var state = this.state;
function emit(withState){
self.manager.emit(self.options.event + (withState ? stateStr(state):''),input);
}
// 'panstart' and 'panmove' if (state < STATE_ENDED){
emit(true);
}
emit();
// simple 'eventName' events // panend and pancancel if (state >= STATE_ENDED){
emit(true);
}
}
,/** * Check that all the require failure recognizers has failed,* if true,it emits a gesture event,* otherwise,setup the state to FAILED. * @param{
Object}
input */
tryEmit:function(input){
if (this.canEmit()){
return this.emit(input);
}
// it's failing anyway this.state = STATE_FAILED;
}
,/** * can we emit? * @returns{
boolean}
*/
canEmit:function(){
var i = 0;
while (i < this.requireFail.length){
if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))){
return false;
}
i++;
}
return true;
}
,/** * update the recognizer * @param{
Object}
inputData */
recognize:function(inputData){
// make a new copy of the inputData // so we can change the inputData without messing up the other recognizers var inputDataClone = extend({
}
,inputData);
// is is enabled and allow recognizing? if (!boolOrFn(this.options.enable,[this,inputDataClone])){
this.reset();
this.state = STATE_FAILED;
return;
}
// reset when we've reached the end if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)){
this.state = STATE_POSSIBLE;
}
this.state = this.process(inputDataClone);
// the recognizer has recognized a gesture // so trigger an event if (this.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED | STATE_CANCELLED)){
this.tryEmit(inputDataClone);
}
}
,/** * return the state of the recognizer * the actual recognizing happens in this method * @virtual * @param{
Object}
inputData * @returns{
Const}
STATE */
process:function(inputData){
}
,// jshint ignore:line /** * return the preferred touch-action * @virtual * @returns{
Array}
*/
getTouchAction:function(){
}
,/** * called when the gesture isn't allowed to recognize * like when another is being recognized or it is disabled * @virtual */
reset:function(){
}
}
;
/** * get a usable string,used as event postfix * @param{
Const}
state * @returns{
String}
state */
function stateStr(state){
if (state & STATE_CANCELLED){
return 'cancel';
}
else if (state & STATE_ENDED){
return 'end';
}
else if (state & STATE_CHANGED){
return 'move';
}
else if (state & STATE_BEGAN){
return 'start';
}
return '';
}
/** * direction cons to string * @param{
Const}
direction * @returns{
String}
*/
function directionStr(direction){
if (direction == DIRECTION_DOWN){
return 'down';
}
else if (direction == DIRECTION_UP){
return 'up';
}
else if (direction == DIRECTION_LEFT){
return 'left';
}
else if (direction == DIRECTION_RIGHT){
return 'right';
}
return '';
}
/** * get a recognizer by name if it is bound to a manager * @param{
Recognizer|String}
otherRecognizer * @param{
Recognizer}
recognizer * @returns{
Recognizer}
*/
function getRecognizerByNameIfManager(otherRecognizer,recognizer){
var manager = recognizer.manager;
if (manager){
return manager.get(otherRecognizer);
}
return otherRecognizer;
}
/** * This recognizer is just used as a base for the simple attribute recognizers. * @constructor * @extends Recognizer */
function AttrRecognizer(){
Recognizer.apply(this,arguments);
}
inherit(AttrRecognizer,Recognizer,{
/** * @namespace * @memberof AttrRecognizer */
defaults:{
/** * @type{
Number}
* @default 1 */
pointers:1}
,/** * Used to check if it the recognizer receives valid input,like input.distance > 10. * @memberof AttrRecognizer * @param{
Object}
input * @returns{
Boolean}
recognized */
attrTest:function(input){
var optionPointers = this.options.pointers;
return optionPointers === 0 || input.pointers.length === optionPointers;
}
,/** * Process the input and return the state for the recognizer * @memberof AttrRecognizer * @param{
Object}
input * @returns{
*}
State */
process:function(input){
var state = this.state;
var eventType = input.eventType;
var isRecognized = state & (STATE_BEGAN | STATE_CHANGED);
var isValid = this.attrTest(input);
// on cancel input and we've recognized before,return STATE_CANCELLED if (isRecognized && (eventType & INPUT_CANCEL || !isValid)){
return state | STATE_CANCELLED;
}
else if (isRecognized || isValid){
if (eventType & INPUT_END){
return state | STATE_ENDED;
}
else if (!(state & STATE_BEGAN)){
return STATE_BEGAN;
}
return state | STATE_CHANGED;
}
return STATE_FAILED;
}
}
);
/** * Pan * Recognized when the pointer is down and moved in the allowed direction. * @constructor * @extends AttrRecognizer */
function PanRecognizer(){
AttrRecognizer.apply(this,arguments);
this.pX = null;
this.pY = null;
}
inherit(PanRecognizer,AttrRecognizer,{
/** * @namespace * @memberof PanRecognizer */
defaults:{
event:'pan',threshold:10,pointers:1,direction:DIRECTION_ALL}
,getTouchAction:function(){
var direction = this.options.direction;
var actions = [];
if (direction & DIRECTION_HORIZONTAL){
actions.push(TOUCH_ACTION_PAN_Y);
}
if (direction & DIRECTION_VERTICAL){
actions.push(TOUCH_ACTION_PAN_X);
}
return actions;
}
,directionTest:function(input){
var options = this.options;
var hasMoved = true;
var distance = input.distance;
var direction = input.direction;
var x = input.deltaX;
var y = input.deltaY;
// lock to axis? if (!(direction & options.direction)){
if (options.direction & DIRECTION_HORIZONTAL){
direction = (x === 0) ? DIRECTION_NONE:(x < 0) ? DIRECTION_LEFT:DIRECTION_RIGHT;
hasMoved = x != this.pX;
distance = Math.abs(input.deltaX);
}
else{
direction = (y === 0) ? DIRECTION_NONE:(y < 0) ? DIRECTION_UP:DIRECTION_DOWN;
hasMoved = y != this.pY;
distance = Math.abs(input.deltaY);
}
}
input.direction = direction;
return hasMoved && distance > options.threshold && direction & options.direction;
}
,attrTest:function(input){
return AttrRecognizer.prototype.attrTest.call(this,input) && (this.state & STATE_BEGAN || (!(this.state & STATE_BEGAN) && this.directionTest(input)));
}
,emit:function(input){
this.pX = input.deltaX;
this.pY = input.deltaY;
var direction = directionStr(input.direction);
if (direction){
this.manager.emit(this.options.event + direction,input);
}
this._super.emit.call(this,input);
}
}
);
/** * Pinch * Recognized when two or more pointers are moving toward (zoom-in) or away from each other (zoom-out). * @constructor * @extends AttrRecognizer */
function PinchRecognizer(){
AttrRecognizer.apply(this,arguments);
}
inherit(PinchRecognizer,AttrRecognizer,{
/** * @namespace * @memberof PinchRecognizer */
defaults:{
event:'pinch',threshold:0,pointers:2}
,getTouchAction:function(){
return [TOUCH_ACTION_NONE];
}
,attrTest:function(input){
return this._super.attrTest.call(this,input) && (Math.abs(input.scale - 1) > this.options.threshold || this.state & STATE_BEGAN);
}
,emit:function(input){
this._super.emit.call(this,input);
if (input.scale !== 1){
var inOut = input.scale < 1 ? 'in':'out';
this.manager.emit(this.options.event + inOut,input);
}
}
}
);
/** * Press * Recognized when the pointer is down for x ms without any movement. * @constructor * @extends Recognizer */
function PressRecognizer(){
Recognizer.apply(this,arguments);
this._timer = null;
this._input = null;
}
inherit(PressRecognizer,Recognizer,{
/** * @namespace * @memberof PressRecognizer */
defaults:{
event:'press',pointers:1,time:500,// minimal time of the pointer to be pressed threshold:5 // a minimal movement is ok,but keep it low}
,getTouchAction:function(){
return [TOUCH_ACTION_AUTO];
}
,process:function(input){
var options = this.options;
var validPointers = input.pointers.length === options.pointers;
var validMovement = input.distance < options.threshold;
var validTime = input.deltaTime > options.time;
this._input = input;
// we only allow little movement // and we've reached an end event,so a tap is possible if (!validMovement || !validPointers || (input.eventType & (INPUT_END | INPUT_CANCEL) && !validTime)){
this.reset();
}
else if (input.eventType & INPUT_START){
this.reset();
this._timer = setTimeoutContext(function(){
this.state = STATE_RECOGNIZED;
this.tryEmit();
}
,options.time,this);
}
else if (input.eventType & INPUT_END){
return STATE_RECOGNIZED;
}
return STATE_FAILED;
}
,reset:function(){
clearTimeout(this._timer);
}
,emit:function(input){
if (this.state !== STATE_RECOGNIZED){
return;
}
if (input && (input.eventType & INPUT_END)){
this.manager.emit(this.options.event + 'up',input);
}
else{
this._input.timeStamp = now();
this.manager.emit(this.options.event,this._input);
}
}
}
);
/** * Rotate * Recognized when two or more pointer are moving in a circular motion. * @constructor * @extends AttrRecognizer */
function RotateRecognizer(){
AttrRecognizer.apply(this,arguments);
}
inherit(RotateRecognizer,AttrRecognizer,{
/** * @namespace * @memberof RotateRecognizer */
defaults:{
event:'rotate',threshold:0,pointers:2}
,getTouchAction:function(){
return [TOUCH_ACTION_NONE];
}
,attrTest:function(input){
return this._super.attrTest.call(this,input) && (Math.abs(input.rotation) > this.options.threshold || this.state & STATE_BEGAN);
}
}
);
/** * Swipe * Recognized when the pointer is moving fast (velocity),with enough distance in the allowed direction. * @constructor * @extends AttrRecognizer */
function SwipeRecognizer(){
AttrRecognizer.apply(this,arguments);
}
inherit(SwipeRecognizer,AttrRecognizer,{
/** * @namespace * @memberof SwipeRecognizer */
defaults:{
event:'swipe',threshold:10,velocity:0.65,direction:DIRECTION_HORIZONTAL | DIRECTION_VERTICAL,pointers:1}
,getTouchAction:function(){
return PanRecognizer.prototype.getTouchAction.call(this);
}
,attrTest:function(input){
var direction = this.options.direction;
var velocity;
if (direction & (DIRECTION_HORIZONTAL | DIRECTION_VERTICAL)){
velocity = input.velocity;
}
else if (direction & DIRECTION_HORIZONTAL){
velocity = input.velocityX;
}
else if (direction & DIRECTION_VERTICAL){
velocity = input.velocityY;
}
return this._super.attrTest.call(this,input) && direction & input.direction && input.distance > this.options.threshold && abs(velocity) > this.options.velocity && input.eventType & INPUT_END;
}
,emit:function(input){
var direction = directionStr(input.direction);
if (direction){
this.manager.emit(this.options.event + direction,input);
}
this.manager.emit(this.options.event,input);
}
}
);
/** * A tap is ecognized when the pointer is doing a small tap/click. Multiple taps are recognized if they occur * between the given interval and position. The delay option can be used to recognize multi-taps without firing * a single tap. * * The eventData from the emitted event contains the property `tapCount`,which contains the amount of * multi-taps being recognized. * @constructor * @extends Recognizer */
function TapRecognizer(){
Recognizer.apply(this,arguments);
// previous time and center,// used for tap counting this.pTime = false;
this.pCenter = false;
this._timer = null;
this._input = null;
this.count = 0;
}
inherit(TapRecognizer,Recognizer,{
/** * @namespace * @memberof PinchRecognizer */
defaults:{
event:'tap',pointers:1,taps:1,interval:300,// max time between the multi-tap taps time:250,// max time of the pointer to be down (like finger on the screen) threshold:2,// a minimal movement is ok,but keep it low posThreshold:10 // a multi-tap can be a bit off the initial position}
,getTouchAction:function(){
return [TOUCH_ACTION_MANIPULATION];
}
,process:function(input){
var options = this.options;
var validPointers = input.pointers.length === options.pointers;
var validMovement = input.distance < options.threshold;
var validTouchTime = input.deltaTime < options.time;
this.reset();
if ((input.eventType & INPUT_START) && (this.count === 0)){
return this.failTimeout();
}
// we only allow little movement // and we've reached an end event,so a tap is possible if (validMovement && validTouchTime && validPointers){
if (input.eventType != INPUT_END){
return this.failTimeout();
}
var validInterval = this.pTime ? (input.timeStamp - this.pTime < options.interval):true;
var validMultiTap = !this.pCenter || getDistance(this.pCenter,input.center) < options.posThreshold;
this.pTime = input.timeStamp;
this.pCenter = input.center;
if (!validMultiTap || !validInterval){
this.count = 1;
}
else{
this.count += 1;
}
this._input = input;
// if tap count matches we have recognized it,// else it has began recognizing... var tapCount = this.count % options.taps;
if (tapCount === 0){
// no failing requirements,immediately trigger the tap event // or wait as long as the multitap interval to trigger if (!this.hasRequireFailures()){
return STATE_RECOGNIZED;
}
else{
this._timer = setTimeoutContext(function(){
this.state = STATE_RECOGNIZED;
this.tryEmit();
}
,options.interval,this);
return STATE_BEGAN;
}
}
}
return STATE_FAILED;
}
,failTimeout:function(){
this._timer = setTimeoutContext(function(){
this.state = STATE_FAILED;
}
,this.options.interval,this);
return STATE_FAILED;
}
,reset:function(){
clearTimeout(this._timer);
}
,emit:function(){
if (this.state == STATE_RECOGNIZED ){
this._input.tapCount = this.count;
this.manager.emit(this.options.event,this._input);
}
}
}
);
/** * Simple way to create an manager with a default set of recognizers. * @param{
HTMLElement}
element * @param{
Object}
[options] * @constructor */
function Hammer(element,options){
options = options ||{
}
;
options.recognizers = ifUndefined(options.recognizers,Hammer.defaults.preset);
return new Manager(element,options);
}
/** * @const{
string}
*/
Hammer.VERSION = '2.0.4';
/** * default settings * @namespace */
Hammer.defaults ={
/** * set if DOM events are being triggered. * But this is slower and unused by simple implementations,so disabled by default. * @type{
Boolean}
* @default false */
domEvents:false,/** * The value for the touchAction property/fallback. * When set to `compute` it will magically set the correct value based on the added recognizers. * @type{
String}
* @default compute */
touchAction:TOUCH_ACTION_COMPUTE,/** * @type{
Boolean}
* @default true */
enable:true,/** * EXPERIMENTAL FEATURE -- can be removed/changed * Change the parent input target element. * If Null,then it is being set the to main element. * @type{
Null|EventTarget}
* @default null */
inputTarget:null,/** * force an input class * @type{
Null|Function}
* @default null */
inputClass:null,/** * Default recognizer setup when calling `Hammer()` * When creating a new Manager these will be skipped. * @type{
Array}
*/
preset:[ // RecognizerClass,options,[recognizeWith,...],[requireFailure,...] [RotateRecognizer,{
enable:false}
],[PinchRecognizer,{
enable:false}
,['rotate']],[SwipeRecognizer,{
direction:DIRECTION_HORIZONTAL}
],[PanRecognizer,{
direction:DIRECTION_HORIZONTAL}
,['swipe']],[TapRecognizer],[TapRecognizer,{
event:'doubletap',taps:2}
,['tap']],[PressRecognizer] ],/** * Some CSS properties can be used to improve the working of Hammer. * Add them to this method and they will be set when creating a new Manager. * @namespace */
cssProps:{
/** * Disables text selection to improve the dragging gesture. Mainly for desktop browsers. * @type{
String}
* @default 'none' */
userSelect:'none',/** * Disable the Windows Phone grippers when pressing an element. * @type{
String}
* @default 'none' */
touchSelect:'none',/** * Disables the default callout shown when you touch and hold a touch target. * On iOS,when you touch and hold a touch target such as a link,Safari displays * a callout containing information about the link. This property allows you to disable that callout. * @type{
String}
* @default 'none' */
touchCallout:'none',/** * Specifies whether zooming is enabled. Used by IE10> * @type{
String}
* @default 'none' */
contentZooming:'none',/** * Specifies that an entire element should be draggable instead of its contents. Mainly for desktop browsers. * @type{
String}
* @default 'none' */
userDrag:'none',/** * Overrides the highlight color shown when the user taps a link or a JavaScript * clickable element in iOS. This property obeys the alpha value,if specified. * @type{
String}
* @default 'rgba(0,0,0,0)' */
tapHighlightColor:'rgba(0,0,0,0)'}
}
;
var STOP = 1;
var FORCED_STOP = 2;
/** * Manager * @param{
HTMLElement}
element * @param{
Object}
[options] * @constructor */
function Manager(element,options){
options = options ||{
}
;
this.options = merge(options,Hammer.defaults);
this.options.inputTarget = this.options.inputTarget || element;
this.handlers ={
}
;
this.session ={
}
;
this.recognizers = [];
this.element = element;
this.input = createInputInstance(this);
this.touchAction = new TouchAction(this,this.options.touchAction);
toggleCssProps(this,true);
each(options.recognizers,function(item){
var recognizer = this.add(new (item[0])(item[1]));
item[2] && recognizer.recognizeWith(item[2]);
item[3] && recognizer.requireFailure(item[3]);
}
,this);
}
Manager.prototype ={
/** * set options * @param{
Object}
options * @returns{
Manager}
*/
set:function(options){
extend(this.options,options);
// Options that need a little more setup if (options.touchAction){
this.touchAction.update();
}
if (options.inputTarget){
// Clean up existing event listeners and reinitialize this.input.destroy();
this.input.target = options.inputTarget;
this.input.init();
}
return this;
}
,/** * stop recognizing for this session. * This session will be discarded,when a new [input]start event is fired. * When forced,the recognizer cycle is stopped immediately. * @param{
Boolean}
[force] */
stop:function(force){
this.session.stopped = force ? FORCED_STOP:STOP;
}
,/** * run the recognizers! * called by the inputHandler function on every movement of the pointers (touches) * it walks through all the recognizers and tries to detect the gesture that is being made * @param{
Object}
inputData */
recognize:function(inputData){
var session = this.session;
if (session.stopped){
return;
}
// run the touch-action polyfill this.touchAction.preventDefaults(inputData);
var recognizer;
var recognizers = this.recognizers;
// this holds the recognizer that is being recognized. // so the recognizer's state needs to be BEGAN,CHANGED,ENDED or RECOGNIZED // if no recognizer is detecting a thing,it is set to `null` var curRecognizer = session.curRecognizer;
// reset when the last recognizer is recognized // or when we're in a new session if (!curRecognizer || (curRecognizer && curRecognizer.state & STATE_RECOGNIZED)){
curRecognizer = session.curRecognizer = null;
}
var i = 0;
while (i < recognizers.length){
recognizer = recognizers[i];
// find out if we are allowed try to recognize the input for this one. // 1. allow if the session is NOT forced stopped (see the .stop() method) // 2. allow if we still haven't recognized a gesture in this session,or the this recognizer is the one // that is being recognized. // 3. allow if the recognizer is allowed to run simultaneous with the current recognized recognizer. // this can be setup with the `recognizeWith()` method on the recognizer. if (session.stopped !== FORCED_STOP && ( // 1 !curRecognizer || recognizer == curRecognizer || // 2 recognizer.canRecognizeWith(curRecognizer))){
// 3 recognizer.recognize(inputData);
}
else{
recognizer.reset();
}
// if the recognizer has been recognizing the input as a valid gesture,we want to store this one as the // current active recognizer. but only if we don't already have an active recognizer if (!curRecognizer && recognizer.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED)){
curRecognizer = session.curRecognizer = recognizer;
}
i++;
}
}
,/** * get a recognizer by its event name. * @param{
Recognizer|String}
recognizer * @returns{
Recognizer|Null}
*/
get:function(recognizer){
if (recognizer instanceof Recognizer){
return recognizer;
}
var recognizers = this.recognizers;
for (var i = 0;
i < recognizers.length;
i++){
if (recognizers[i].options.event == recognizer){
return recognizers[i];
}
}
return null;
}
,/** * add a recognizer to the manager * existing recognizers with the same event name will be removed * @param{
Recognizer}
recognizer * @returns{
Recognizer|Manager}
*/
add:function(recognizer){
if (invokeArrayArg(recognizer,'add',this)){
return this;
}
// remove existing var existing = this.get(recognizer.options.event);
if (existing){
this.remove(existing);
}
this.recognizers.push(recognizer);
recognizer.manager = this;
this.touchAction.update();
return recognizer;
}
,/** * remove a recognizer by name or instance * @param{
Recognizer|String}
recognizer * @returns{
Manager}
*/
remove:function(recognizer){
if (invokeArrayArg(recognizer,'remove',this)){
return this;
}
var recognizers = this.recognizers;
recognizer = this.get(recognizer);
recognizers.splice(inArray(recognizers,recognizer),1);
this.touchAction.update();
return this;
}
,/** * bind event * @param{
String}
events * @param{
Function}
handler * @returns{
EventEmitter}
this */
on:function(events,handler){
var handlers = this.handlers;
each(splitStr(events),function(event){
handlers[event] = handlers[event] || [];
handlers[event].push(handler);
}
);
return this;
}
,/** * unbind event,leave emit blank to remove all handlers * @param{
String}
events * @param{
Function}
[handler] * @returns{
EventEmitter}
this */
off:function(events,handler){
var handlers = this.handlers;
each(splitStr(events),function(event){
if (!handler){
delete handlers[event];
}
else{
handlers[event].splice(inArray(handlers[event],handler),1);
}
}
);
return this;
}
,/** * emit event to the listeners * @param{
String}
event * @param{
Object}
data */
emit:function(event,data){
// we also want to trigger dom events if (this.options.domEvents){
triggerDomEvent(event,data);
}
// no handlers,so skip it all var handlers = this.handlers[event] && this.handlers[event].slice();
if (!handlers || !handlers.length){
return;
}
data.type = event;
data.preventDefault = function(){
data.srcEvent.preventDefault();
}
;
var i = 0;
while (i < handlers.length){
handlers[i](data);
i++;
}
}
,/** * destroy the manager and unbinds all events * it doesn't unbind dom events,that is the user own responsibility */
destroy:function(){
this.element && toggleCssProps(this,false);
this.handlers ={
}
;
this.session ={
}
;
this.input.destroy();
this.element = null;
}
}
;
/** * add/remove the css properties as defined in manager.options.cssProps * @param{
Manager}
manager * @param{
Boolean}
add */
function toggleCssProps(manager,add){
var element = manager.element;
each(manager.options.cssProps,function(value,name){
element.style[prefixed(element.style,name)] = add ? value:'';
}
);
}
/** * trigger dom event * @param{
String}
event * @param{
Object}
data */
function triggerDomEvent(event,data){
var gestureEvent = document.createEvent('Event');
gestureEvent.initEvent(event,true,true);
gestureEvent.gesture = data;
data.target.dispatchEvent(gestureEvent);
}
extend(Hammer,{
INPUT_START:INPUT_START,INPUT_MOVE:INPUT_MOVE,INPUT_END:INPUT_END,INPUT_CANCEL:INPUT_CANCEL,STATE_POSSIBLE:STATE_POSSIBLE,STATE_BEGAN:STATE_BEGAN,STATE_CHANGED:STATE_CHANGED,STATE_ENDED:STATE_ENDED,STATE_RECOGNIZED:STATE_RECOGNIZED,STATE_CANCELLED:STATE_CANCELLED,STATE_FAILED:STATE_FAILED,DIRECTION_NONE:DIRECTION_NONE,DIRECTION_LEFT:DIRECTION_LEFT,DIRECTION_RIGHT:DIRECTION_RIGHT,DIRECTION_UP:DIRECTION_UP,DIRECTION_DOWN:DIRECTION_DOWN,DIRECTION_HORIZONTAL:DIRECTION_HORIZONTAL,DIRECTION_VERTICAL:DIRECTION_VERTICAL,DIRECTION_ALL:DIRECTION_ALL,Manager:Manager,Input:Input,TouchAction:TouchAction,TouchInput:TouchInput,MouseInput:MouseInput,PointerEventInput:PointerEventInput,TouchMouseInput:TouchMouseInput,SingleTouchInput:SingleTouchInput,Recognizer:Recognizer,AttrRecognizer:AttrRecognizer,Tap:TapRecognizer,Pan:PanRecognizer,Swipe:SwipeRecognizer,Pinch:PinchRecognizer,Rotate:RotateRecognizer,Press:PressRecognizer,on:addEventListeners,off:removeEventListeners,each:each,merge:merge,extend:extend,inherit:inherit,bindFn:bindFn,prefixed:prefixed}
);
if (typeof define == TYPE_FUNCTION && define.amd){
define(function(){
return Hammer;
}
);
}
else if (typeof module != 'undefined' && module.exports){
module.exports = Hammer;
}
else{
window[exportName] = Hammer;
}
}
)(window,document,'Hammer');
JS代码(main.js):
TweenLite.defaultEase = Expo.easeOut;
var utils ={
map:function (value,min1,max1,min2,max2){
var value1 = (value - min1) / (max1 - min1);
var value2 = value1 * (max2 - min2) + min2;
return value2;
}
}
;
var UIButton = function (){
'use strict';
var UIButton = function ($element){
this.$element = $element;
this.$shadow = this.$element.find('.btn__shadow');
this.clickableArea ={
x:0,y:0,w:this.$element.width(),h:this.$element.height()}
;
this.handlers ={
}
;
this.$element.on('click',function (evt){
var offsetX = evt.pageX - this.$element.offset().left;
var offsetY = evt.pageY - this.$element.offset().top;
if (offsetX >= this.clickableArea.x && offsetX <= this.clickableArea.x + this.clickableArea.w && offsetY >= this.clickableArea.y && offsetY <= this.clickableArea.y + this.clickableArea.h){
this.trigger('click',evt);
}
}
.bind(this));
this.on('click',this.shadow.bind(this));
}
;
UIButton.prototype.shadow = function (){
var timeline = new TimelineMax();
timeline.set(this.$shadow,{
opacity:0.15}
);
timeline.to(this.$shadow,1,{
opacity:0}
);
}
;
UIButton.prototype.on = function (name,fn){
if (typeof name === 'string' && typeof fn === 'function'){
this.handlers[name] = (this.handlers[name] || []).concat(fn);
}
}
;
UIButton.prototype.trigger = function (name){
var args = Array.prototype.slice.call(arguments);
for (var i = 0;
i < (this.handlers[name] || []).length;
i++){
this.handlers[name][i].apply(this,args.slice(1));
}
}
;
return UIButton;
}
();
var PlayPauseUIButton = function (){
'use strict';
var PlayPauseUIButton = function ($element){
UIButton.call(this,$element);
this.timeline = new TimelineMax({
paused:true}
);
this.timeline.staggerTo(this.$element.find('.icon__shape--circle'),1.75,{
strokeDasharray:'119.38052 0',strokeDashoffset:-119.38052}
,0.05,0);
this.timeline.to(this.$element.find('.icon__shape--triangle'),1.75,{
strokeDasharray:'38 0',strokeDashoffset:38}
,0);
this.timeline.to(this.$element.find('.icon__shape--line'),0.5,{
strokeDashoffset:-12}
,0);
this.timeline.to(this.$element.find('.icon__shape--line'),0.5,{
opacity:0}
,0.1);
this.on('click',this.toggle);
}
;
PlayPauseUIButton.prototype = Object.create(UIButton.prototype);
PlayPauseUIButton.prototype.toggle = function (){
if (this.timeline.yoyo()){
this.pause();
}
else{
this.play();
}
}
;
PlayPauseUIButton.prototype.play = function (){
this.timeline.yoyo(true).tweenTo(this.timeline.duration());
}
;
PlayPauseUIButton.prototype.pause = function (){
this.timeline.yoyo(false).tweenTo(0,{
ease:Expo.easeOut}
);
}
;
return PlayPauseUIButton;
}
();
var VolumeUIButton = function (){
'use strict';
var VolumeUIButton = function ($element){
UIButton.call(this,$element);
this.level = 2;
this.progress = 50;
this.volume = 50;
this.clickableArea.w = 45;
this.gestureManager = new Hammer.Manager(this.$element.find('.icon__shape--circle-controls')[0],{
touchAction:'none'}
);
this.gestureManager.add(new Hammer.Press({
time:10}
));
this.gestureManager.add(new Hammer.Pan({
direction:Hammer.DIRECTION_HORIZONTAL,threshold:0}
));
this.timeline = new TimelineMax();
this.on('click',this.toggle);
}
;
VolumeUIButton.prototype = Object.create(UIButton.prototype);
VolumeUIButton.prototype.shadow = function (){
if (!this.timeline.yoyo()){
UIButton.prototype.shadow.call(this);
}
}
;
VolumeUIButton.prototype.toggle = function (evt){
if (!this.timeline.yoyo()){
this.open();
}
}
;
VolumeUIButton.prototype.open = function (){
var tweens = [];
this.timeline.yoyo(true).clear();
tweens.push(new TweenMax.to(this.$element.find('.icon__shape--circle-small'),0.75,{
strokeDashoffset:10.99557}
));
tweens.push(new TweenMax.to(this.$element.find('.icon__shape--circle-medium'),0.75,{
opacity:1,strokeDasharray:'0 163.36281',strokeDashoffset:-10.21017}
));
tweens.push(new TweenMax.to(this.$element.find('.icon__shape--circle-big'),0.75,{
strokeDashoffset:-119.38052}
));
this.timeline.add(tweens);
var tweens = [];
tweens.push(new TweenMax.to(this.$element.find('.icon__shape--circle-small'),0.5,{
opacity:0}
));
tweens.push(new TweenMax.to(this.$element.find('.icon__shape--circle-big'),0.5,{
opacity:0}
));
this.timeline.add(tweens,0.25);
this.timeline.set(this.$element.find('.icon__shape--circle-medium'),{
opacity:0}
);
this.timeline.set(this.$element.find('.icon__shape--circle-placeholder'),{
opacity:1}
);
var tweens = [];
tweens.push(new TweenMax.to(this.$element.find('.icon__shape--line-controls.icon__shape--translucide'),0.5,{
strokeDashoffset:0}
));
tweens.push(new TweenMax.to(this.$element.find('.icon__shape--circle-controls'),0.5,{
attr:{
r:8}
,ease:Back.easeOut}
));
this.timeline.add(tweens,0.75);
this.timeline.to({
progress:0}
,0.5 * this.volume / 100,{
progress:this.volume,onUpdate:function (tween){
this.progress = tween.target.progress;
this.refresh();
}
,onUpdateParams:['{
self}
'],onUpdateScope:this}
,0.75);
this.timeline.add(this.drag.bind(this));
}
;
VolumeUIButton.prototype.close = function (){
var tweens = [];
this.timeline.yoyo(false).clear();
this.timeline.to({
progress:this.volume}
,0.5 * this.volume / 100,{
progress:0,onUpdate:function (tween){
this.progress = tween.target.progress;
this.refresh();
}
,onUpdateParams:['{
self}
'],onUpdateScope:this}
,'progressive');
tweens.push(new TweenMax.to(this.$element.find('.icon__shape--line-controls.icon__shape--translucide'),0.5,{
strokeDashoffset:125}
));
tweens.push(new TweenMax.to(this.$element.find('.icon__shape--circle-controls'),0.5,{
attr:{
r:0}
}
));
this.timeline.add(tweens,'progressive-=0.5');
this.timeline.set(this.$element.find('.icon__shape--circle-medium'),{
opacity:1}
);
this.timeline.set(this.$element.find('.icon__shape--circle-placeholder'),{
opacity:0}
);
tweens = [];
tweens.push(new TweenMax.to(this.$element.find('.icon__shape--circle-small'),0.5,{
opacity:!this.level ? 0.2:1}
));
tweens.push(new TweenMax.to(this.$element.find('.icon__shape--circle-big'),0.5,{
opacity:this.level < 3 ? 0.2:1}
));
tweens.push(new TweenMax.to(this.$element.find('.icon__shape--circle-small'),0.75,{
strokeDashoffset:0}
));
tweens.push(new TweenMax.to(this.$element.find('.icon__shape--circle-medium'),0.75,{
opacity:this.level < 2 ? 0.2:1,strokeDasharray:'20.42035 163.36281',strokeDashoffset:0}
));
tweens.push(new TweenMax.to(this.$element.find('.icon__shape--circle-big'),0.75,{
strokeDashoffset:-89.53539}
));
this.timeline.add(tweens);
this.gestureManager.off('panstart panmove pancancel panend');
TweenMax.killTweensOf(self.close);
}
;
VolumeUIButton.prototype.drag = function (){
var self = this;
var volume = 0;
this.gestureManager.on('press',function (){
TweenMax.to(self.$element.find('.icon__shape--circle-controls'),0.5,{
attr:{
r:10}
,opacity:0.4,ease:Back.easeOut}
);
TweenMax.killTweensOf(self.close);
}
);
this.gestureManager.on('panstart',function (){
volume = self.volume;
}
);
this.gestureManager.on('panmove',function (evt){
self.progress = self.volume = Math.min(Math.max(volume + evt.deltaX * 100 / 125,0),100);
self.refresh();
}
);
this.gestureManager.on('pressup',this.release.bind(this));
this.gestureManager.on('pancancel panend',this.release.bind(this));
TweenMax.delayedCall(3,self.close,null,self);
}
;
VolumeUIButton.prototype.release = function (){
TweenMax.to(this.$element.find('.icon__shape--circle-controls'),0.5,{
attr:{
r:8}
,opacity:0.2}
);
TweenMax.delayedCall(3,this.close,null,this);
}
;
VolumeUIButton.prototype.refresh = function (){
var level = 0;
if (this.volume){
level = Math.floor(utils.map(this.volume,0,101,0,3)) + 1;
}
if (level !== this.level){
if (!this.level){
this.timeline.clear();
this.timeline.staggerTo(this.$element.find('.icon__shape--line-mute'),0.5,{
strokeDashoffset:35.3553}
,-0.1);
this.timeline.staggerTo(this.$element.find('.icon__shape--line-mute'),0.5,{
opacity:0}
,-0.1,0.25);
}
else if (!level){
this.timeline.clear();
this.timeline.set(this.$element.find('.icon__shape--line-mute'),{
opacity:1}
);
this.timeline.staggerTo(this.$element.find('.icon__shape--line-mute'),0.5,{
strokeDashoffset:0}
,0.1);
}
this.level = level;
}
TweenMax.set(this.$element.find('.icon__shape--line-controls.icon__shape--white'),{
strokeDashoffset:125 * (1 - this.progress / 100)}
);
TweenMax.set(this.$element.find('.icon__shape--circle-controls'),{
x:125 * this.progress / 100}
);
this.trigger('volumeChange',this.volume);
}
;
return VolumeUIButton;
}
();
$('.btn--play-pause').each(function (){
new PlayPauseUIButton($(this));
}
);
$('.btn--volume').each(function (){
var btn = new VolumeUIButton($(this));
btn.on('volumeChange',function (vol){
console.log(vol);
}
);
}
);
CSS代码(styles.css):
body{background-color:rgba(0,0,0,0.75);color:#ffffff;overflow:hidden;font-family:'Montserrat',sans-serif;font-weight:700;-webkit-font-smoothing:antialiased;}
*{box-sizing:border-box;}
.view__menu{background-color:#000000;bottom:0;left:0;height:120px;position:absolute;width:100%;}
.view__menu .menu__inner{left:0;margin-top:-20px;padding:0 40px;position:absolute;top:50%;width:100%;}
.view__menu .menu__credits{color:#333333;float:right;font-size:14px;letter-spacing:1px;line-height:40px;}
.view__menu .menu__link{color:#ffffff;display:inline-block;position:relative;text-decoration:none;text-transform:uppercase;}
.view__menu .menu__link:after{background-color:#ffffff;content:'';height:2px;left:0;margin-top:10px;position:absolute;top:50%;width:100%;-webkit-transition:-webkit-transform 500ms cubic-bezier(0.19,1,0.22,1);transition:transform 500ms cubic-bezier(0.19,1,0.22,1);-webkit-transform-origin:left center;-ms-transform-origin:left center;transform-origin:left center;-webkit-transform:scaleX(0);-ms-transform:scaleX(0);transform:scaleX(0);}
.view__menu .menu__link:hover:after{-webkit-transform:scaleX(1);-ms-transform:scaleX(1);transform:scaleX(1);}
.view__menu .btn{float:left;margin-right:40px;position:relative;}
.view__menu .btn .btn__icon{height:40px;}
.view__menu .btn .btn__shadow{background-color:#ffffff;border-radius:50%;left:-15px;height:70px;opacity:0;pointer-events:none;position:absolute;top:-15px;width:70px;}
.view__menu .btn .btn__icon .icon__shape{fill:none;stroke:#ffffff;stroke-linecap:round;stroke-linejoin:round;stroke-width:2;}
.view__menu .btn .btn__icon .icon__shape--translucide{opacity:0.2;}
.view__menu .btn .btn__icon .icon__shape--orange{stroke:#ffa700;}
.view__menu .btn--play-pause .btn__icon .icon__shape--circle{height:40px;stroke-dasharray:119.38052 119.38052;width:40px;}
.view__menu .btn--play-pause .btn__icon .icon__shape--line{stroke-dasharray:12 12;}
.view__menu .btn--play-pause .btn__icon .icon__shape--triangle{stroke-dasharray:12 38;}
.view__menu .btn--volume .btn__shadow{left:-12px;}
.view__menu .btn--volume .btn__icon .icon__shape--line-mute{stroke-dasharray:35.3553 70.71067;stroke-dashoffset:35.3553;}
.view__menu .btn--volume .btn__icon .icon__shape--line-controls{stroke-dasharray:125 250;stroke-dashoffset:125;}
.view__menu .btn--volume .btn__icon .icon__shape--circle-small{opacity:1;stroke-dasharray:10.99557 87.96459;}
.view__menu .btn--volume .btn__icon .icon__shape--circle-medium{opacity:1;stroke-dasharray:20.42035 163.36281;}
.view__menu .btn--volume .btn__icon .icon__shape--circle-big{stroke-dasharray:29.84513 238.76104;stroke-dashoffset:-89.53539;}
.view__menu .btn--volume .btn__icon .icon__shape--circle-controls{cursor:pointer;display:block;fill:#ffffff;opacity:0.2;stroke:none;}
.view__menu .btn--volume .btn__icon .icon__shape--circle-placeholder{fill:#ffffff;opacity:0;stroke:none;}