-
Notifications
You must be signed in to change notification settings - Fork 18
/
Copy pathdice.js
446 lines (417 loc) · 14.1 KB
/
dice.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
/*!
* author:jieyou
* contacts:baidu hi->youyo1122
* see https://github.com/jieyou/dice
*/
;(function(window,document,parseInt){
// 初始化时各个面的rotate角度 // 初始状态下,2面向用户
var diceStart = [
null
,{x:90,y:0,z:0} // rotateX,rotateY,rotate
,{x:0,y:0,z:0}
,{x:0,y:90,z:0}
,{x:0,y:180,z:0}
,{x:0,y:-90,z:0}
,{x:-90,y:0,z:180}
]
// 6个结果对应的,需要将wrapper rotate的角度
var diceResult = [
null
,{x:270,y:0,z:0} // roll出1
,{x:0,y:0,z:0} // roll出2 // 初始状态下,2面向用户
,{x:0,y:270,z:0} // roll出3
,{x:180,y:0,z:180}
,{x:0,y:90,z:0}
,{x:90,y:0,z:180}
]
// 色子面共同和动画效果无关的css
var faceStyle = {
'position':'absolute'
,'border':'5px solid #fff'
//,'borderRadius':'5px' // todo:圆角大小可配 // done
,'backgroundColor':'#fff'
,'fontSize':'0'
,'textAlign':'center'
}
function callType(obj){
return Object.prototype.toString.call(obj)
}
var isArray = Array.isArray || function(arr){
return callType(arr) === '[object Array]'
}
var forEach = Array.prototype.forEach || function(callback){
var i = 0
,t = this
,len = t.length
for(;i<len;i++){
callback.call(t[i],t[i],i)
}
}
// 各色子面单独html
var faceHtml = {
tpl:'<div class="dice_circle" style="#{margin};#{border};left:#{left}%;top:#{top}%;width:0;height:0;position:absolute;"></div>'
,position:[
null
,[[50,50]]
,[[50,30],[50,70]]
,[[28,72],[50,50],[72,28]]
,[[30,30],[30,70],[70,30],[70,70]]
,[[28,28],[28,72],[50,50],[72,28],[72,72]]
,[[28,22.5],[28,50],[28,77.5],[72,22.5],[72,50],[72,77.5]]
]
,html:[null]
}
;(function(faceHtml){
forEach.call(faceHtml.position,function(e,i){
var html = []
if(e){
forEach.call(e,function(e_,i_){
html.push(faceHtml.tpl.replace('#{left}',e_[0]).replace('#{top}',e_[1]))
})
faceHtml.html[i] = html.join('')
}
})
delete faceHtml.tpl
delete faceHtml.position
})(faceHtml)
function emptyFn(){}
function on(dom,event,func){
if(dom.addEventListener){
dom.addEventListener(event,func,false)
}else{
dom.attachEvent('on'+event,func)
}
}
function off(dom,event,func){
if(dom.removeEventListener){
dom.removeEventListener(event,func,false)
}else{
dom.detachEvent('on'+event,func)
}
}
function getRightJsStyleName(cssKey,prefixWord){
if(prefixWord){
return prefixWord+cssKey[0].toUpperCase()+cssKey.substr(1)
}
return cssKey
}
// 检测是否支持 `transform-style:preserve-3d`
var supportPreserve3d = (function(){
// 安卓3.0以下(不含)通过此方法无法检测(即使在不支持的情况下,使用getComputedStyle或直接div.style[styleName]还是会返回'preserve-3d'),这里写搓一点
// IE均不支持,但IE9无法通过此法来检测,这里写搓一点
// 后来发现UC也有问题,这里写搓一点
// 后来发现遨游也有问题,这里写搓一点
// 已经在zhihu发帖准备解决这个问题:
var isAndroid = navigator.userAgent.match(/(Android);?[\s\/]+([\d.]+)?/)
,version
,isAndorid3Below = false
,isIE = /MSIE/i.test(navigator.userAgent)
,isUC = /UCBrowser/i.test(navigator.userAgent)
,isMaxthon = /Maxthon/i.test(navigator.userAgent)
,div = document.createElement('DIV')
if(isAndroid){
version = parseFloat(isAndroid[2])
isAndorid3Below = !isNaN(version) && version < 3
}
return function(prefixWord){
var styleName
if(isAndorid3Below || isIE || isUC || isMaxthon){
return false
}
styleName = getRightJsStyleName('transformStyle',prefixWord)
div.style[styleName] = 'preserve-3d'
return !!div.style[styleName].length
}
})()
// 确定制造商前缀及支持preserve-3d情况,不光是有前缀,同时支持3d动画时prefix才不为null
var prefix
var prefixWord
;(function(style,string){
if(typeof style.webkitTransform === string && supportPreserve3d('webkit')){
prefix = '-webkit-'
prefixWord = 'webkit'
}else if(typeof style.MozTransform === string && supportPreserve3d('Moz')){
prefix = '-moz-'
prefixWord = 'Moz'
}else if(typeof style.msTransform === string && supportPreserve3d('ms')){ // IE10及以下 都不支持 所需关键属性 `transform-style:preserve-3d` 故暂时不兼容IE
prefix = '-ms-'
prefixWord = 'ms'
}else if(typeof style.transform === string && supportPreserve3d('')){
prefix = prefixWord = ''
}else{
prefix = prefixWord = null
}
// 调试不支持的情况的代码,将prefix、prefixWord写死为null
// prefix = prefixWord = null
})(document.body.style,'string')
// console.log(prefix)
// 标准化和降级兼容原生的requestAnimationFrame
// 安卓4.4以下(不含)不支持requestAnimationFrame
var requestAnimationFrame_ = window.requestAnimationFrame
|| window[getRightJsStyleName('requestAnimationFrame',prefixWord)]
|| function(callback){
return setTimeout(callback,17)
}
// 调试setTimeout
// var requestAnimationFrame_ =function(callback){
// return setTimeout(callback,17)
// }
var cancelAnimationFrame_ = window.cancelAnimationFrame
|| window[getRightJsStyleName('cancelAnimationFrame',prefixWord)]
|| clearTimeout
// 调试clearTimeout
// var cancelAnimationFrame_ = clearTimeout
// 在参数数组中获取一个随机数
function random(arr) {
var len = arr.length
// if(isArray(arr)){ // 注释掉判断是否为一个数组的部分,在入口函数中已经确保了
// len = arr.length
if(len === 1){
return arr[0]
}
return arr[Math.floor(Math.random()*len)]
// }else{
// return arr
// }
}
// 赋予css
function css(domOrCssStr,oneKeyOrKeyValueObject,oneValue){
var key
// if(arguments.length === 1){ // 已经不存在需要拼装的css文本了,暂时注释掉
// key = document.createElement('STYLE')
// key.innerHTML = domOrCssStr
// document.getElementsByTagName('HEAD')[0].appendChild(key)
// }
if(arguments.length === 3){
domOrCssStr.style[oneKeyOrKeyValueObject] = oneValue
}else{
for(key in oneKeyOrKeyValueObject){
if(oneKeyOrKeyValueObject.hasOwnProperty(key)){
domOrCssStr.style[key] = oneKeyOrKeyValueObject[key]
}
}
}
}
function keepAnimation(dice){
// 目前firefox 30 在第二次roll的时候keepAnimation阶段显示不正常或没有动作,暂时没有办法修复
dice.dtime = new Date().getTime() - dice.stime
if(dice.dtime>dice.keepAnimationTime){
dice._onKeepAnimationEnd()
endAnimation(dice)
return
}
if(prefix !== null){
dice.rafId = requestAnimationFrame_(function(){ // 最开始进来的时候也考虑requestAnimationFrame_ 提高性能
keepAnimation(dice)
})
// var a = 'rotateZ(' + dice.zAngle + 'deg) rotateX(' + dice.xAngle + 'deg) rotateY(' + dice.yAngle +'deg)'
// (prefix==='-moz-'?'':prefix)+'transform'
css(dice.wrapper,getRightJsStyleName('transform',prefixWord),'rotate(' + dice.zAngle + 'deg) rotateX(' + dice.xAngle + 'deg) rotateY(' + dice.yAngle +'deg)')
// todo:可以顺时针转或逆时针,也就是angle既可以+10,也可以-10
dice.curRollIsRotateX?dice.yAngle = 0:dice.xAngle = 0
dice.zAngle = dice.zAngle>360? dice.zAngle-360 : dice.zAngle+10
// x与y任意旋转一个即可,随机一下
dice.curRollIsRotateX?
(dice.xAngle = dice.xAngle>360? dice.xAngle-360 : dice.xAngle+10):
(dice.yAngle = dice.yAngle>360? dice.yAngle-360 : dice.yAngle+10)
}
}
function endAnimation(dice){
var val
cancelAnimationFrame_(dice.rafId)
dice.rafId = null
dice.dtime = 0
val = diceResult[dice.result]
// off first
on(dice.wrapper,getRightJsStyleName('transitionEnd',prefixWord),dice._onEndAnimationEnd)
on(dice.wrapper,getRightJsStyleName('transitionEnd',prefixWord),dice._onRollEnd)
css(dice.wrapper,getRightJsStyleName('transition',prefixWord),prefix+'transform '+(dice.endAnimationTime/1000)+'s ease-out')
css(dice.wrapper,getRightJsStyleName('transform',prefixWord),'rotate(' + val.z + 'deg) rotateX(' + val.x + 'deg) rotateY(' + val.y +'deg)')
// dice.onEndAnimationEnd.call(dice,dice.result) // 更多的参数待定
}
function noAnimation(dice){
var circles
forEach.call([1,2,3,4,5,6],function(e){
var face = dice['face'+e]
if(e === dice.result){
circles = face.getElementsByTagName('DIV') // IE8- 不支持getElementsByClassname,好在除此之外没有别的元素了
forEach.call(circles,function(c){
c.style.display = 'none'
})
face.style.zIndex = '2000'
}else{
face.style.zIndex = '1000'
}
})
setTimeout(function(){
forEach.call(circles,function(c){
c.style.display = 'block'
})
dice._onNoAnimationEnd()
dice._onRollEnd()
},200) // todo:时间可配
}
function Dice(container,formattedConfig){
var t = this
forEach.call(['edgeLength','radius','hasShadow','shadowTop','keepAnimationTime','endAnimationTime'],function(e,i){
t[e] = formattedConfig[e]
})
t._onKeepAnimationEnd = function(){
formattedConfig.onKeepAnimationEnd.call(t,t.result)
}
t._onEndAnimationEnd = function(){
formattedConfig.onEndAnimationEnd.call(t,t.result)
}
t._onNoAnimationEnd = function(){
formattedConfig.onNoAnimationEnd.call(t,t.result)
}
t._onRollEnd = function(){
formattedConfig.onRollEnd.call(t,t.result)
}
// t.translateZ // 默认与棱长一致
// 下面是static
t.zAngle = 0 // x轴rotate初始状态
t.yAngle = 0
t.xAngle = 0
t.rafId = null // requestAnimationFrame 返回的id
t.stime = 0
t.dtime = 0
// 底部阴影
if(t.hasShadow){
t.shadow = document.createElement('DIV')
css(t.shadow,{
'position': 'absolute'
,'left': '50%'
,'top': t.shadowTop+'%'
,'height': '0'
,'width': t.edgeLength*4/10+'px'
// ,'boxShadow': '0 0 '+t.edgeLength+'px '+t.edgeLength*35/100+'px rgba(0, 0, 0, 0.7)'
,'marginLeft': '-'+t.edgeLength*2/10+'px'
})
// firefox 30 不认识 -moz-box-shadow,但认识box-shadow
css(t.shadow,prefixWord==='Moz'?'boxShadow':getRightJsStyleName('boxShadow',prefixWord),'0 0 '+t.edgeLength+'px '+t.edgeLength*35/100+'px rgba(0, 0, 0, 0.7)')
t.shadow.className = 'dice_shadow'
container.appendChild(t.shadow)
}
forEach.call(['wrapper','face1','face2','face3','face4','face5','face6'],function(e,i){
t[e] = document.createElement('DIV')
if(e === 'wrapper'){
// t[e].style.a
css(t[e],{
'position': 'absolute'
,'height': t.edgeLength + 'px'
,'width': t.edgeLength + 'px'
,'top': '50%'
,'left': '50%'
,'marginLeft': '-'+(t.edgeLength/2)+'px'
,'marginTop': '-'+(t.edgeLength/2)+'px'
})
if(prefix !== null){
css(t[e],getRightJsStyleName('transformStyle',prefixWord),'preserve-3d')
// css(t[e],getRightJsStyleName('transform',prefixWord),'translateX(0px)')
css(t[e],getRightJsStyleName('transformOrigin',prefixWord),'50% 50%')
// css(t[e],getRightJsStyleName('transition',prefix+'transform 0s ease-in-out')
}
t[e].className = 'dice_wrapper'
}else{
css(t[e],faceStyle)
css(t[e],{
'height':(t.edgeLength-10)+'px' // 10为每个面的圆角,如果圆角也要自适应棱长,则这个10的数字也要更改为计算出来的
,'width':(t.edgeLength-10)+'px'
// ,'lineHeight':(t.edgeLength-10)+'px'
,'borderRadius':t.radius+'px'
})
// css(t[e],getRightJsStyleName('backfaceVisibility',prefixWord),'hidden')
if(prefix !== null){
css(t[e],getRightJsStyleName('transform',prefixWord),'rotateX('+diceStart[i].x+'deg) rotateY('+diceStart[i].y+'deg) rotate('+diceStart[i].z+'deg) translateZ('+(t.edgeLength/2)+'px)')
// css(t[e],getRightJsStyleName('perspective',prefixWord),'800px')
}
t[e].className = 'dice_face dice_' + e
t[e].innerHTML = faceHtml.html[i].replace(/\#\{margin\}/g,'margin-top:-'+(t.edgeLength/10)+'px;margin-left:-'+(t.edgeLength/10)+'px').replace(/\#\{border\}/g,'border:'+(t.edgeLength/10)+'px #'+((i===1||i===4)?'F':'0')+'00 solid;border-radius:'+(t.edgeLength/10)+'px')
t.wrapper.appendChild(t[e])
}
})
css(container,'position','relative')
if(prefix !== null){
css(container,getRightJsStyleName('perspective',prefixWord),'800px')
css(container,getRightJsStyleName('perspectiveOrigin',prefixWord),'50% 50%')
}
container.appendChild(t.wrapper)
}
Dice.prototype.roll = function(valueArr){ // todo:提供单次roll的 keepAnimationTime,endAnimationTime,thisRollOnKeepAnimationEnd,thisRollOnEndAnimationEnd 可配,只在这一次roll中生效
var t = this
,_valueArr
,v
,timeLen
,isS = false
if(t.rafId !== null){
return
}
if(isArray(valueArr)){
_valueArr = []
forEach.call(valueArr,function(e,i){
v = parseInt(e,10)
if(v>=1 && v<=6){
_valueArr.push(v)
}
})
}else if(typeof(valueArr) === 'number'){
_valueArr = [valueArr]
}
if(!_valueArr || (isArray(_valueArr) && !_valueArr.length)){
_valueArr = [1,2,3,4,5,6]
}
t.result = random(_valueArr)
if(prefix !== null){
t.curRollIsRotateX = !!random([0,1])
css(t.wrapper,prefix+'transition',prefix+'transform 0s linear')
t.stime = new Date().getTime()
off(t.wrapper,getRightJsStyleName('transitionEnd',prefixWord),t._onEndAnimationEnd)
off(t.wrapper,getRightJsStyleName('transitionEnd',prefixWord),t._onRollEnd)
requestAnimationFrame_(function(){keepAnimation(t)})
}else{
noAnimation(t)
}
return t.result
}
Dice.prototype.stop = function(){
var t = this
if(t.rafId !== null){
cancelAnimationFrame_(t.rafId)
t.rafId = null
}
}
// 可配项和默认配置
var defaultConfig = {
edgeLength:100
,radius:5
,hasShadow:true
,shadowTop:75
,keepAnimationTime:3000
,endAnimationTime:1000
,onKeepAnimationEnd:emptyFn
,onEndAnimationEnd:emptyFn
,onNoAnimationEnd:emptyFn
,onRollEnd:emptyFn
}
window.dice = function(container,userConfig){
var k
,formattedConfig
if(arguments.length === 0){
return
}
if(!userConfig){
formattedConfig = defaultConfig
}else{
formattedConfig = {}
for(k in defaultConfig){
if(userConfig.hasOwnProperty(k) && defaultConfig.hasOwnProperty(k) && callType(defaultConfig[k]) === callType(userConfig[k])){
formattedConfig[k] = userConfig[k]
}else{
formattedConfig[k] = defaultConfig[k]
}
}
}
return new Dice(container,formattedConfig)
}
})(window,document,parseInt)