├── .eslintignore ├── .gitignore ├── examples ├── images │ ├── dial_bg.png │ ├── scratch_no.png │ ├── tiger_bg.png │ ├── dial_page_bg.jpg │ ├── dial_pointer.png │ ├── placeholder.jpg │ ├── scratch_ceiling.jpg │ ├── scratch_page_bg.jpg │ ├── tiger_awards_1.png │ ├── tiger_awards_2.png │ ├── tiger_awards_3.png │ └── tiger_page_bg.jpg ├── card.html ├── dial.html ├── tiger.html └── css │ └── style.css ├── .eslintrc.js ├── src ├── modules │ ├── assign.js │ ├── getPrefix.js │ ├── animationEnd.js │ ├── requestAnimationFrame.js │ └── events.js ├── dial.js ├── tiger.js └── card.js ├── package.json ├── dist ├── dial.js ├── tiger.js └── card.js └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | /dist -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | npm-debug.log 4 | node_modules 5 | -------------------------------------------------------------------------------- /examples/images/dial_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/94cstyles/lottery/HEAD/examples/images/dial_bg.png -------------------------------------------------------------------------------- /examples/images/scratch_no.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/94cstyles/lottery/HEAD/examples/images/scratch_no.png -------------------------------------------------------------------------------- /examples/images/tiger_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/94cstyles/lottery/HEAD/examples/images/tiger_bg.png -------------------------------------------------------------------------------- /examples/images/dial_page_bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/94cstyles/lottery/HEAD/examples/images/dial_page_bg.jpg -------------------------------------------------------------------------------- /examples/images/dial_pointer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/94cstyles/lottery/HEAD/examples/images/dial_pointer.png -------------------------------------------------------------------------------- /examples/images/placeholder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/94cstyles/lottery/HEAD/examples/images/placeholder.jpg -------------------------------------------------------------------------------- /examples/images/scratch_ceiling.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/94cstyles/lottery/HEAD/examples/images/scratch_ceiling.jpg -------------------------------------------------------------------------------- /examples/images/scratch_page_bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/94cstyles/lottery/HEAD/examples/images/scratch_page_bg.jpg -------------------------------------------------------------------------------- /examples/images/tiger_awards_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/94cstyles/lottery/HEAD/examples/images/tiger_awards_1.png -------------------------------------------------------------------------------- /examples/images/tiger_awards_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/94cstyles/lottery/HEAD/examples/images/tiger_awards_2.png -------------------------------------------------------------------------------- /examples/images/tiger_awards_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/94cstyles/lottery/HEAD/examples/images/tiger_awards_3.png -------------------------------------------------------------------------------- /examples/images/tiger_page_bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/94cstyles/lottery/HEAD/examples/images/tiger_page_bg.jpg -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | node: true 6 | }, 7 | extends: 'standard', 8 | plugins: [ 9 | 'html' 10 | ], 11 | globals: { 12 | 'Vue': false 13 | }, 14 | 'rules': { 15 | 'arrow-parens': 0, 16 | 'prefer-const': ['error', { 17 | 'destructuring': 'any', 18 | 'ignoreReadBeforeAssign': false 19 | }], 20 | 'space-before-function-paren': ['error', { 21 | 'anonymous': 'ignore', 22 | 'named': 'ignore', 23 | 'asyncArrow': 'ignore' 24 | }] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/modules/assign.js: -------------------------------------------------------------------------------- 1 | Object.assign = Object.assign || 2 | function (target) { 3 | // We must check against these specific cases. 4 | if (target === undefined || target === null) { 5 | throw new TypeError('Cannot convert undefined or null to object') 6 | } 7 | 8 | const output = Object(target) 9 | for (let index = 1; index < arguments.length; index++) { 10 | const source = arguments[index] 11 | if (source !== undefined && source !== null) { 12 | for (const nextKey in source) { 13 | if (source.hasOwnProperty(nextKey)) { 14 | output[nextKey] = source[nextKey] 15 | } 16 | } 17 | } 18 | } 19 | return output 20 | } 21 | 22 | export default Object.assign 23 | -------------------------------------------------------------------------------- /src/modules/getPrefix.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 获取css属性前缀 null:不支持该属性 3 | * @param el 用于校验的element 4 | * @param property css属性 5 | * @param value 样式值 设置该值是会进行赋值 6 | * @returns {string || null} 7 | */ 8 | export default function (el, property, value) { 9 | function camelCase (str) { 10 | return str.replace(/-([a-z])/ig, function (all, letter) { 11 | return letter.toUpperCase() 12 | }) 13 | } 14 | 15 | if (el.style[property] === undefined) { 16 | for (const vendor of ['webkit', 'ms', 'moz', 'o', null]) { 17 | if (!vendor) return null 18 | property = camelCase(vendor + '-' + property) 19 | if (el.style[property] !== undefined) { 20 | break 21 | } 22 | } 23 | } 24 | if (value) el.style[property] = value 25 | 26 | return property 27 | } 28 | -------------------------------------------------------------------------------- /src/modules/animationEnd.js: -------------------------------------------------------------------------------- 1 | const animationEvent = function () { 2 | const el = document.createElement('div') 3 | const animations = { 4 | 'animation': 'animationend', 5 | 'webkitAnimation': 'webkitAnimationEnd', 6 | 'msAnimation': 'MSAnimationEnd', 7 | 'oAnimation': 'oanimationend' 8 | } 9 | 10 | for (const t in animations) { 11 | if (el.style[t] !== undefined) { 12 | return animations[t] 13 | } 14 | } 15 | 16 | return null 17 | }() 18 | 19 | /** 20 | * 处理animate动画结束时间 21 | * @param el 绑定事件目标元素 22 | * @param callback 回调函数 23 | * @param animateTime 当不支持animationend使用settimeout处理 延迟时间 24 | */ 25 | export default function (el, callback, animateTime = 0) { 26 | function bind () { 27 | callback() 28 | el.removeEventListener(animationEvent, bind) 29 | } 30 | 31 | animationEvent ? el.addEventListener(animationEvent, bind) : setTimeout(() => callback(), animateTime) 32 | } 33 | -------------------------------------------------------------------------------- /src/modules/requestAnimationFrame.js: -------------------------------------------------------------------------------- 1 | let lastTime = 0 2 | export const requestAnimationFrame = window.requestAnimationFrame || 3 | window['msRequestAnimationFrame'] || 4 | window['mozRequestAnimationFrame'] || 5 | window['webkitRequestAnimationFrame'] || 6 | window['oRequestAnimationFrame'] || 7 | function (callback) { 8 | const currTime = new Date().getTime() 9 | const timeToCall = Math.max(0, 16 - (currTime - lastTime)) 10 | const id = window.setTimeout(() => { 11 | callback(currTime + timeToCall) // eslint-disable-line 12 | }, timeToCall) 13 | 14 | lastTime = currTime + timeToCall 15 | return id 16 | } 17 | 18 | export const cancelAnimationFrame = window.cancelAnimationFrame || 19 | window['msCancelAnimationFrame'] || 20 | window['mozCancelAnimationFrame'] || 21 | window['webkitCancelAnimationFrame'] || 22 | window['oCancelAnimationFrame'] || 23 | function (id) { 24 | clearInterval(id) 25 | } 26 | -------------------------------------------------------------------------------- /src/modules/events.js: -------------------------------------------------------------------------------- 1 | export default class Events { 2 | constructor () { 3 | this._queue = [] 4 | } 5 | 6 | on (key, callback) { 7 | this._queue[key] = this._queue[key] || [] 8 | this._queue[key].push(callback) 9 | return this 10 | } 11 | 12 | off (key, callback) { 13 | if (this._queue[key]) { 14 | const index = typeof (callback) === 'undefined' ? -2 : this._queue[key].indexOf(callback) 15 | if (index === -2) { 16 | delete this._queue[key] 17 | } else if (index !== -1) { 18 | this._queue[key].splice(index, 1) 19 | } 20 | if (this._queue[key] && this._queue[key].length === 0) delete this._queue[key] 21 | } 22 | return this 23 | } 24 | 25 | has (key) { 26 | return !!this._queue[key] 27 | } 28 | 29 | trigger (key, ...args) { 30 | if (this._queue[key]) { 31 | this._queue[key].forEach((callback) => callback.apply(this, args)) 32 | } 33 | return this 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lottery", 3 | "version": "1.0.0", 4 | "description": "移动端抽奖插件[大转盘,老虎机,刮刮卡]", 5 | "main": "./dist/lottery.js", 6 | "scripts": { 7 | "dev": "NODE_ENV=development node build/index.js", 8 | "release": "NODE_ENV=production node build/index.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/94cstyles/lottery.git" 13 | }, 14 | "author": "cstyles", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/94cstyles/lottery/issues" 18 | }, 19 | "homepage": "https://github.com/94cstyles/lottery#readme", 20 | "devDependencies": { 21 | "babel-preset-es2015-loose-rollup": "^7.0.0", 22 | "eslint": "^3.19.0", 23 | "eslint-config-standard": "^10.2.1", 24 | "eslint-plugin-html": "^2.0.1", 25 | "eslint-plugin-import": "^2.2.0", 26 | "eslint-plugin-node": "^4.2.2", 27 | "eslint-plugin-promise": "^3.5.0", 28 | "eslint-plugin-standard": "^3.0.1", 29 | "rollup": "^0.41.6", 30 | "rollup-plugin-babel": "^2.7.1", 31 | "rollup-plugin-uglify": "^1.0.2" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/card.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 刮刮卡 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/dial.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 大转盘 11 | 12 | 13 | 14 |
15 |
16 | 17 |
18 |
19 | 20 | 21 | 40 | 41 | -------------------------------------------------------------------------------- /examples/tiger.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 老虎机 11 | 12 | 13 | 14 |
15 |
16 | 27 |
28 |
29 | 40 |
41 |
42 | 53 |
54 | 55 |
56 | 57 | 58 | 71 | 72 | -------------------------------------------------------------------------------- /src/dial.js: -------------------------------------------------------------------------------- 1 | import './modules/assign' 2 | import Events from './modules/events' 3 | import getPrefix from './modules/getPrefix' 4 | import { requestAnimationFrame, cancelAnimationFrame } from './modules/requestAnimationFrame' 5 | 6 | class LotteryDial extends Events { 7 | constructor (pointer, options) { 8 | super() 9 | 10 | this.options = Object.assign({ 11 | speed: 30, // 每帧速度 12 | areaNumber: 8, // 奖区数量 13 | deviation: 2 // 随机结果角度偏差值 为了防止出现指针和扇区分割线无限重合 单位:° 14 | }, options) 15 | this.pointer = pointer 16 | 17 | this.init() 18 | } 19 | 20 | init () { 21 | // 初始化样式设定 22 | this._transform = getPrefix(this.pointer, 'transform', 'translate3d(0,0,0)') 23 | getPrefix(this.pointer, 'backfaceVisibility', 'hidden') 24 | getPrefix(this.pointer, 'perspective', '1000px') 25 | 26 | this._raf = null 27 | this._runAngle = 0 28 | this._targetAngle = -1 29 | } 30 | 31 | reset (event = 'reset') { 32 | if (!this._raf) return 33 | cancelAnimationFrame(this._raf) 34 | this._raf = null 35 | this._runAngle = 0 36 | this._targetAngle = -1 37 | this.trigger(event) 38 | if (event === 'reset') getPrefix(this.pointer, this._transform, 'translate3d(0,0,0) rotate(0deg)') 39 | } 40 | 41 | setResult (index) { 42 | // 得到中奖结果 index:中奖奖区下标 43 | const singleAngle = 360 / this.options.areaNumber // 单个奖区角度值 44 | let endAngle = Math.random() * singleAngle // 随机得出结果在扇区内的角度 45 | 46 | endAngle = Math.max(this.options.deviation, endAngle) 47 | endAngle = Math.min(singleAngle - this.options.deviation, endAngle) 48 | endAngle = Math.ceil(endAngle + (index * singleAngle)) 49 | 50 | this._runAngle = 0 51 | this._targetAngle = endAngle + (Math.floor(Math.random() * 4) + 4) * 360 // 随机旋转几圈再停止 52 | } 53 | 54 | step () { 55 | // 如果没有设置结束点 就匀速不停旋转 56 | // 如果设置了结束点 就减速到达结束点 57 | if (this._targetAngle === -1) { 58 | this._runAngle += this.options.speed 59 | } else { 60 | this._angle = (this._targetAngle - this._runAngle) / this.options.speed 61 | this._angle = this._angle > this.options.speed ? this.options.speed : this._angle < 0.5 ? 0.5 : this._angle 62 | this._runAngle += this._angle 63 | this._runAngle = this._runAngle > this._targetAngle ? this._targetAngle : this._runAngle 64 | } 65 | // 指针旋转 66 | getPrefix(this.pointer, this._transform, 'translate3d(0,0,0) rotate(' + (this._runAngle % 360) + 'deg)') 67 | 68 | if (this._runAngle === this._targetAngle) { 69 | this.reset('end') 70 | } else { 71 | this._raf = requestAnimationFrame(() => this.step()) 72 | } 73 | } 74 | 75 | draw () { 76 | if (this._raf) return 77 | if (this.has('start')) this.trigger('start') 78 | this._angle = 0 79 | this._raf = requestAnimationFrame(() => this.step()) 80 | } 81 | } 82 | 83 | export default LotteryDial 84 | -------------------------------------------------------------------------------- /src/tiger.js: -------------------------------------------------------------------------------- 1 | import './modules/assign' 2 | import Events from './modules/events' 3 | import animationEnd from './modules/animationEnd' 4 | 5 | class LotteryTigerRoller { 6 | constructor (elem) { 7 | this.elem = elem 8 | this.items = elem.children 9 | 10 | // 克隆第一个节点 用于制作无限滚动效果 11 | this.elem.appendChild(this.items[0].cloneNode(true)) 12 | } 13 | 14 | resize () { 15 | this.height = this.items[0].clientHeight 16 | if (!this.elem.classList.contains('fx-roll') && this.index > 0) this.elem.style.marginTop = -this.index * this.height + 'px' 17 | } 18 | 19 | reset () { 20 | this.elem.classList.remove('fx-roll') 21 | this.elem.classList.remove('fx-bounce') 22 | this.elem.style.marginTop = 0 23 | this.state = 0 24 | } 25 | 26 | start (timeout = 0) { 27 | if (this.state === 1) return 28 | this.state = 1 29 | setTimeout(() => { 30 | if (this.state !== 1) return 31 | this.elem.classList.add('fx-roll') 32 | this.elem.style.marginTop = 0 33 | }, timeout) 34 | } 35 | 36 | stop (index, callback, timeout = 0) { 37 | if (!this.height) this.height = this.items[0].clientHeight 38 | setTimeout(() => { 39 | if (this.state !== 1) return 40 | this.elem.classList.remove('fx-roll') 41 | this.elem.classList.add('fx-bounce') 42 | this.elem.style.marginTop = -index * this.height + 'px' 43 | animationEnd(this.elem, () => { 44 | this.state = 0 45 | this.elem.classList.remove('fx-bounce') 46 | if (callback) callback.call(this, index) 47 | }) 48 | }, timeout) 49 | } 50 | } 51 | 52 | class LotteryTiger extends Events { 53 | constructor (toggle, rollers, options) { 54 | super() 55 | 56 | this.options = Object.assign({ 57 | interval: 300, // 每个roller间动画间隔 58 | aniMinTime: 6000, // 动画执行最少时间 59 | resize: true // roller大小是否是可变的 60 | }, options) 61 | this.toggle = toggle 62 | 63 | // 初始化滚轴 64 | this.rollerQueue = [] 65 | for (let i = 0; i < rollers.length; i++) { 66 | this.rollerQueue.push(new LotteryTigerRoller(rollers[i])) 67 | } 68 | 69 | // 如果大小是可变的就绑定resize事件 70 | if (this.options.resize) { 71 | window.addEventListener('onorientationchange' in document ? 'orientationchange' : 'resize', () => { 72 | this.rollerQueue.forEach((roller) => roller.resize()) 73 | }) 74 | } 75 | } 76 | 77 | reset () { 78 | this.toggle.classList.remove('z-active') 79 | for (let i = 0, l = this.rollerQueue.length; i < l; i++) { 80 | this.rollerQueue[i].reset() 81 | } 82 | this.trigger('reset') 83 | } 84 | 85 | setResult (ret) { 86 | // 保证动画执行时间 87 | const endTime = (new Date()).getTime() 88 | setTimeout(() => { 89 | for (let i = 0, l = this.rollerQueue.length; i < l; i++) { 90 | this.rollerQueue[i].stop(ret[i], (i === l - 1 ? () => { 91 | this.toggle.classList.remove('z-active') 92 | this.trigger('end') 93 | } : null), i * this.options.interval) 94 | } 95 | }, endTime - this._startTime > this.options.aniMinTime ? 0 : this.options.aniMinTime - (endTime - this._startTime)) 96 | } 97 | 98 | draw () { 99 | if (this.toggle.classList.contains('z-active')) return 100 | if (this.has('start')) this.trigger('start') 101 | this._startTime = (new Date()).getTime() 102 | 103 | this.toggle.classList.add('z-active') 104 | for (let i = 0, l = this.rollerQueue.length; i < l; i++) { 105 | this.rollerQueue[i].start(i * this.options.interval) 106 | } 107 | } 108 | } 109 | 110 | export default LotteryTiger 111 | -------------------------------------------------------------------------------- /dist/dial.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.LotteryDial=e()}(this,function(){"use strict";Object.assign=Object.assign||function(t){if(void 0===t||null===t)throw new TypeError("Cannot convert undefined or null to object");for(var e=Object(t),n=1;n1?n-1:0),r=1;r0&&void 0!==arguments[0]?arguments[0]:"reset";this._raf&&(a(this._raf),this._raf=null,this._runAngle=0,this._targetAngle=-1,this.trigger(t),"reset"===t&&r(this.pointer,this._transform,"translate3d(0,0,0) rotate(0deg)"))},o.prototype.setResult=function(t){var e=360/this.options.areaNumber,n=Math.random()*e;n=Math.max(this.options.deviation,n),n=Math.min(e-this.options.deviation,n),n=Math.ceil(n+t*e),this._runAngle=0,this._targetAngle=n+360*(Math.floor(4*Math.random())+4)},o.prototype.step=function(){var t=this;-1===this._targetAngle?this._runAngle+=this.options.speed:(this._angle=(this._targetAngle-this._runAngle)/this.options.speed,this._angle=this._angle>this.options.speed?this.options.speed:this._angle<.5?.5:this._angle,this._runAngle+=this._angle,this._runAngle=this._runAngle>this._targetAngle?this._targetAngle:this._runAngle),r(this.pointer,this._transform,"translate3d(0,0,0) rotate("+this._runAngle%360+"deg)"),this._runAngle===this._targetAngle?this.reset("end"):this._raf=s(function(){return t.step()})},o.prototype.draw=function(){var t=this;this._raf||(this.has("start")&&this.trigger("start"),this._angle=0,this._raf=s(function(){return t.step()}))},o}(i)}); 2 | -------------------------------------------------------------------------------- /dist/tiger.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.LotteryTiger=t()}(this,function(){"use strict";Object.assign=Object.assign||function(e){if(void 0===e||null===e)throw new TypeError("Cannot convert undefined or null to object");for(var t=Object(e),i=1;i1?i-1:0),o=1;o2&&void 0!==arguments[2]?arguments[2]:0;o?e.addEventListener(o,i):setTimeout(function(){return t()},n)},s=function(){function t(i){e(this,t),this.elem=i,this.items=i.children,this.elem.appendChild(this.items[0].cloneNode(!0))}return t.prototype.resize=function(){this.height=this.items[0].clientHeight,!this.elem.classList.contains("fx-roll")&&this.index>0&&(this.elem.style.marginTop=-this.index*this.height+"px")},t.prototype.reset=function(){this.elem.classList.remove("fx-roll"),this.elem.classList.remove("fx-bounce"),this.elem.style.marginTop=0,this.state=0},t.prototype.start=function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0;1!==this.state&&(this.state=1,setTimeout(function(){1===e.state&&(e.elem.classList.add("fx-roll"),e.elem.style.marginTop=0)},t))},t.prototype.stop=function(e,t){var i=this,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0;this.height||(this.height=this.items[0].clientHeight),setTimeout(function(){1===i.state&&(i.elem.classList.remove("fx-roll"),i.elem.classList.add("fx-bounce"),i.elem.style.marginTop=-e*i.height+"px",r(i.elem,function(){i.state=0,i.elem.classList.remove("fx-bounce"),t&&t.call(i,e)}))},n)},t}();return function(n){function o(t,r,u){e(this,o);var l=i(this,n.call(this));l.options=Object.assign({interval:300,aniMinTime:6e3,resize:!0},u),l.toggle=t,l.rollerQueue=[];for(var a=0;athis.options.aniMinTime?0:this.options.aniMinTime-(i-this._startTime))},o.prototype.draw=function(){if(!this.toggle.classList.contains("z-active")){this.has("start")&&this.trigger("start"),this._startTime=(new Date).getTime(),this.toggle.classList.add("z-active");for(var e=0,t=this.rollerQueue.length;e 6 | start => (next) 抽奖前触发,主要用于抽奖信息处理,传递回调函数next,需进行调用。
7 | end => () 抽奖结束触发
8 | reset => () 抽奖重置触发,主要出现调用了lottery.reset()
9 | 10 | lottery都包含了3个操作函数 [setResult,reset,draw]
11 | setResult => (result) 设置抽奖结果
12 | reset => () 重置抽奖
13 | draw => () 开始抽奖(注:刮刮卡调用此方法后会直接显示结果)
14 | 15 | ### 刮刮卡 16 | 17 | ```html 18 | 19 | 20 | ``` 21 | 22 | ```javascript 23 | var lottery = new LotteryCard(document.getElementById('js_lottery'),{ 24 | size: 20, //滑动区域大小 25 | percent: 50, //激活百分比到谋个值 就全显示 26 | resize: true, //canvas的大小是否是可变的 27 | cover: null //img or color string, default gray 28 | }); 29 | lottery.on('start',function(){ 30 | //中奖结果,传递是中奖结果图片地址 31 | lottery.setResult('...imageSrc'); 32 | }).on('end',function(){}).on('reset',function(){}); 33 | ``` 34 | 35 | ### 大转盘 36 | 37 | ```html 38 |
39 |
40 | 41 |
42 |
43 | ``` 44 | 45 | ```javascript 46 | var lottery = new LotteryDial(document.getElementById('js_pointer'), { 47 | speed: 30, //每帧速度 48 | areaNumber: 8, //奖区数量 49 | deviation: 2 //随机结果角度偏差值 为了防止出现指针和扇区分割线无限重合 单位:° 50 | }); 51 | var index = -1; 52 | lottery.on('start', function () { 53 | //请求获取中奖结果 54 | index = Math.round(Math.random() * 7); 55 | //中奖结果,传递停留奖区下标0开始 56 | lottery.setResult(index); 57 | }).on('end', function () { 58 | console.log('中奖奖区:' + index); 59 | }).on('reset',function(){}); 60 | ``` 61 | 62 | ### 老虎机 63 | 64 | ```html 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 | ```javascript 112 | //老虎机动画因为性能问题采用css3 所以需要完成2个动画fx-bounce fx-roll 详细查看examples 113 | var lottery = new LotteryTiger(document.getElementById('js_toggle'), document.querySelectorAll('.roller'), { 114 | interval: 300, //每个roller间动画间隔 115 | aniMinTime: 6000, //动画执行最少时间 116 | resize: true //roller大小是否是可变的 117 | }); 118 | lottery.on('start', function () { 119 | setTimeout(function () { 120 | var ret = [Math.round(Math.random() * 2), Math.round(Math.random() * 2), Math.round(Math.random() * 2)]; 121 | //中奖结果,传递每个roller停留下标0开始 122 | lottery.setResult(ret); 123 | }, 1000); 124 | }).on('end', function(){}).on('reset',function(){}); 125 | ``` 126 | 127 | 128 | ## 浏览器支持 129 | 130 | ![Chrome](https://raw.github.com/alrra/browser-logos/master/chrome/chrome_48x48.png) | ![Firefox](https://raw.github.com/alrra/browser-logos/master/firefox/firefox_48x48.png) | ![IE](https://raw.github.com/alrra/browser-logos/master/internet-explorer/internet-explorer_48x48.png) | ![Opera](https://raw.github.com/alrra/browser-logos/master/opera/opera_48x48.png) | ![Safari](https://raw.github.com/alrra/browser-logos/master/safari/safari_48x48.png) 131 | --- | --- | --- | --- | --- | 132 | Latest ✔ | Latest ✔ | 10+ ✔ | Latest ✔ | Latest ✔ | -------------------------------------------------------------------------------- /dist/card.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.LotteryCard=e()}(this,function(){"use strict";Object.assign=Object.assign||function(t){if(void 0===t||null===t)throw new TypeError("Cannot convert undefined or null to object");for(var e=Object(t),i=1;i1?i-1:0),o=1;o=this.options.percent&&(this.ctx.clearRect(0,0,this.width,this.height),this._state="end",this.trigger("end"))}},o.prototype.onResize=function(){this._touch=!1,this.options.resize&&"end"!==this._state?this.init():this.getCanvasInfo()},o}(n)}); 2 | -------------------------------------------------------------------------------- /src/card.js: -------------------------------------------------------------------------------- 1 | import './modules/assign' 2 | import Events from './modules/events' 3 | 4 | class LotteryCard extends Events { 5 | constructor (canvas, options) { 6 | super() 7 | 8 | this.options = Object.assign({ 9 | size: 20, // 滑动区域大小 10 | percent: 50, // 激活百分比 到该值就显示结果 11 | resize: true, // canvas大小是否是可变的 12 | cover: null 13 | }, options) 14 | 15 | this.canvas = canvas 16 | this.ctx = canvas.getContext('2d') 17 | 18 | this._first = true 19 | this._touch = false 20 | this.init() 21 | this.bind() 22 | } 23 | 24 | getCanvasInfo () { 25 | const info = this.canvas.getBoundingClientRect() 26 | const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0 27 | const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeftp || 0 28 | 29 | this.width = info.width 30 | this.height = info.height 31 | this.offsetX = Math.round(info.left + scrollLeft) 32 | this.offsetY = Math.round(info.top + scrollTop) 33 | this.canvas.width = info.width 34 | this.canvas.height = info.height 35 | } 36 | 37 | bind () { 38 | const SUPPORT_ONLY_TOUCH = ('ontouchstart' in window) && /mobile|tablet|ip(ad|hone|od)|android/i.test(navigator.userAgent) // 是否支持touch 39 | 40 | this.canvas.addEventListener(SUPPORT_ONLY_TOUCH ? 'touchstart' : 'mousedown', this.onTouchStart.bind(this), false) 41 | this.canvas.addEventListener(SUPPORT_ONLY_TOUCH ? 'touchmove' : 'mousemove', this.onTouchMove.bind(this), false) 42 | document.addEventListener(SUPPORT_ONLY_TOUCH ? 'touchend' : 'mouseup', this.onTouchEnd.bind(this)) 43 | window.addEventListener('onorientationchange' in document ? 'orientationchange' : 'resize', this.onResize.bind(this)) 44 | } 45 | 46 | init () { 47 | this._state = 'init' 48 | this.getCanvasInfo() 49 | 50 | // 绘制遮罩层 51 | this.ctx.closePath() 52 | this.ctx.globalCompositeOperation = 'source-over' 53 | const cover = this.options.cover 54 | if (cover instanceof Image) { 55 | this.ctx.fillStyle = this.ctx.createPattern(cover, 'repeat') 56 | this.ctx.rect(0, 0, this.width, this.height) 57 | } else { 58 | this.ctx.fillStyle = typeof cover === 'string' ? cover : 'gray' 59 | this.ctx.fillRect(0, 0, this.width, this.height) 60 | } 61 | this.ctx.fill() 62 | this.ctx.globalCompositeOperation = 'destination-out' 63 | } 64 | 65 | reset () { 66 | this._first = true 67 | this._touch = false 68 | this.canvas.style.backgroundImage = null 69 | this.init() 70 | this.trigger('reset') 71 | } 72 | 73 | setResult (url) { 74 | this.canvas.style.backgroundImage = 'url(' + url + ')' // 设置结果 75 | } 76 | 77 | draw () { 78 | if (this._state === 'end') return 79 | this._state = 'end' 80 | this.ctx.clearRect(0, 0, this.width, this.height) 81 | this.trigger('end') 82 | } 83 | 84 | scratchPercent () { 85 | const imageData = this.ctx.getImageData(0, 0, this.width, this.height) 86 | let hits = 0 87 | 88 | for (let i = 0, ii = imageData.data.length; i < ii; i = i + 4) { 89 | if (imageData.data[i] === 0 && imageData.data[i + 1] === 0 && imageData.data[i + 2] === 0 && imageData.data[i + 3] === 0) { 90 | hits++ 91 | } 92 | } 93 | 94 | return (hits / (this.width * this.height)) * 100 95 | } 96 | 97 | getEventXY (e) { 98 | e = e.changedTouches ? e.changedTouches[0] : e 99 | return { 100 | x: e.pageX - this.offsetX, 101 | y: e.pageY - this.offsetY 102 | } 103 | } 104 | 105 | onTouchStart (e) { 106 | e.preventDefault() 107 | if (this._state === 'end') return 108 | if (this.has('start') && this._first) this.trigger('start') 109 | 110 | // 绘制起点 111 | const point = this.getEventXY(e) 112 | this._state = 'start' 113 | this._touch = true 114 | this._first = false 115 | 116 | this.ctx.beginPath() 117 | this.ctx.arc(point.x, point.y, this.options.size / 2, 0, Math.PI * 2, true) 118 | this.ctx.closePath() 119 | this.ctx.fill() 120 | 121 | this.ctx.beginPath() 122 | this.ctx.lineWidth = this.options.size 123 | this.ctx.moveTo(point.x, point.y) 124 | } 125 | 126 | onTouchMove (e) { 127 | e.preventDefault() 128 | if (!this._touch) return 129 | // 绘制路线 130 | const point = this.getEventXY(e) 131 | this.ctx.lineTo(point.x, point.y) 132 | this.ctx.stroke() 133 | } 134 | 135 | onTouchEnd (e) { 136 | if (!this._touch) return 137 | this._touch = false 138 | 139 | // 绘制终点 140 | const point = this.getEventXY(e) 141 | this.ctx.closePath() 142 | this.ctx.beginPath() 143 | this.ctx.arc(point.x, point.y, this.options.size / 2, 0, Math.PI * 2, true) 144 | this.ctx.closePath() 145 | this.ctx.fill() 146 | 147 | // 计算已经刮掉的面积 148 | if (this.scratchPercent() >= this.options.percent) { 149 | this.ctx.clearRect(0, 0, this.width, this.height) 150 | this._state = 'end' 151 | this.trigger('end') 152 | } 153 | } 154 | 155 | onResize () { 156 | this._touch = false 157 | if (this.options.resize) { 158 | if (this._state !== 'end') { 159 | this.init() 160 | } else { 161 | this.getCanvasInfo() 162 | } 163 | } else { 164 | this.getCanvasInfo() 165 | } 166 | } 167 | } 168 | 169 | export default LotteryCard 170 | -------------------------------------------------------------------------------- /examples/css/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0 4 | } 5 | 6 | html { 7 | font-size: 20px; 8 | -webkit-text-size-adjust: 100%; 9 | -ms-text-size-adjust: 100% 10 | } 11 | 12 | html, body { 13 | width: 100%; 14 | height: 100%; 15 | -webkit-user-select: none; 16 | -ms-user-select: none; 17 | -moz-user-select: none; 18 | user-select: none 19 | } 20 | 21 | audio, canvas, progress, video { 22 | display: inline-block; 23 | vertical-align: baseline 24 | } 25 | 26 | img, video { 27 | max-width: 100%; 28 | height: auto; 29 | border: 0 none; 30 | vertical-align: bottom 31 | } 32 | 33 | li { 34 | list-style: none 35 | } 36 | 37 | svg:not(:root) { 38 | overflow: hidden 39 | } 40 | 41 | a { 42 | text-decoration: none 43 | } 44 | 45 | a, img, button, input[type='button'], input[type='submit'], input[type='reset'] { 46 | outline: none; 47 | -webkit-tap-highlight-color: rgba(255, 255, 255, 0) 48 | } 49 | 50 | input:-ms-clear { 51 | display: none 52 | } 53 | 54 | input[type='search']::-webkit-search-cancel-button { 55 | display: none 56 | } 57 | 58 | input, textarea { 59 | -webkit-user-modify: read-write-plaintext-only; 60 | -webkit-appearance: none; 61 | outline: none 62 | } 63 | 64 | button, input, textarea { 65 | border: 0 none 66 | } 67 | 68 | body, button, input, textarea, select, option, pre, optgroup { 69 | line-height: 1.5; 70 | font-family: "Microsoft YaHei", "微软雅黑", Helvetica, Arial; 71 | color: #333; 72 | font-size: 0.6rem 73 | } 74 | 75 | .f-cb:after, .f-cb li:after { 76 | content: ''; 77 | display: table; 78 | clear: both 79 | } 80 | 81 | .f-wsn { 82 | word-wrap: normal; 83 | white-space: nowrap 84 | } 85 | 86 | .f-wwb { 87 | white-space: normal; 88 | word-wrap: break-word; 89 | word-break: break-all 90 | } 91 | 92 | .f-toe { 93 | overflow: hidden; 94 | word-wrap: normal; 95 | white-space: nowrap; 96 | text-overflow: ellipsis 97 | } 98 | 99 | .f-ma { 100 | margin-left: auto; 101 | margin-right: auto 102 | } 103 | 104 | .f-box { 105 | -webkit-box-sizing: border-box; 106 | -moz-box-sizing: border-box; 107 | box-sizing: border-box 108 | } 109 | 110 | .m-alert { 111 | position: fixed; 112 | top: 0; 113 | left: 0; 114 | z-index: 10000; 115 | width: 100%; 116 | height: 100%; 117 | background-color: transparent 118 | } 119 | 120 | .m-alert .text { 121 | width: 11rem; 122 | padding: 1.25rem 0.75rem; 123 | text-align: center; 124 | line-height: 1.4rem; 125 | font-size: 0.8rem; 126 | color: #fff; 127 | background-color: rgba(0, 0, 0, 0.7); 128 | white-space: normal; 129 | word-wrap: break-word; 130 | word-break: break-all; 131 | position: absolute; 132 | top: 50%; 133 | left: 50%; 134 | -webkit-transform: translate(-50%, -50%); 135 | transform: translate(-50%, -50%); 136 | border-radius: 0.25rem 137 | } 138 | 139 | .m-alert.fx-fadeIn, .m-alert.fx-fadeOut { 140 | -webkit-animation-duration: 0.3s; 141 | animation-duration: 0.3s; 142 | -webkit-animation-fill-mode: ease; 143 | animation-fill-mode: ease 144 | } 145 | 146 | [class*='fx-'] { 147 | -webkit-animation-duration: 1s; 148 | animation-duration: 1s; 149 | -webkit-animation-fill-mode: both; 150 | animation-fill-mode: both 151 | } 152 | 153 | .fx-bug { 154 | -webkit-transform-style: preserve-3d; 155 | transform-style: preserve-3d; 156 | -webkit-backface-visibility: visible; 157 | backface-visibility: visible 158 | } 159 | 160 | .fx-gpu, .m-lottery-tiger .game .item .roller { 161 | -webkit-transform: "translateZ(0)"; 162 | transform: "translateZ(0)"; 163 | -webkit-backface-visibility: hidden; 164 | backface-visibility: hidden; 165 | -webkit-perspective: 1000px; 166 | perspective: 1000px 167 | } 168 | 169 | .fx-infinite { 170 | -webkit-animation-iteration-count: infinite; 171 | animation-iteration-count: infinite 172 | } 173 | 174 | .fx-fadeIn { 175 | -webkit-animation-name: fadeIn; 176 | animation-name: fadeIn 177 | } 178 | 179 | @-webkit-keyframes fadeIn { 180 | 0% { 181 | opacity: 0 182 | } 183 | 100% { 184 | opacity: 1 185 | } 186 | } 187 | 188 | @keyframes fadeIn { 189 | 0% { 190 | opacity: 0 191 | } 192 | 100% { 193 | opacity: 1 194 | } 195 | } 196 | 197 | .fx-fadeOut { 198 | -webkit-animation-name: fadeOut; 199 | animation-name: fadeOut 200 | } 201 | 202 | @-webkit-keyframes fadeOut { 203 | 0% { 204 | opacity: 1 205 | } 206 | 100% { 207 | opacity: 0 208 | } 209 | } 210 | 211 | @keyframes fadeOut { 212 | 0% { 213 | opacity: 1 214 | } 215 | 100% { 216 | opacity: 0 217 | } 218 | } 219 | 220 | @media screen and (min-width: 320px) { 221 | html { 222 | font-size: 20px 223 | } 224 | } 225 | 226 | @media screen and (min-width: 360px) { 227 | html { 228 | font-size: 22.5px 229 | } 230 | } 231 | 232 | @media screen and (min-width: 375px) { 233 | html { 234 | font-size: 23.4375px 235 | } 236 | } 237 | 238 | @media screen and (min-width: 384px) { 239 | html { 240 | font-size: 24px 241 | } 242 | } 243 | 244 | @media screen and (min-width: 400px) { 245 | html { 246 | font-size: 25px 247 | } 248 | } 249 | 250 | @media screen and (min-width: 412px) { 251 | html { 252 | font-size: 25.75px 253 | } 254 | } 255 | 256 | @media screen and (min-width: 414px) { 257 | html { 258 | font-size: 25.875px 259 | } 260 | } 261 | 262 | @media screen and (min-width: 533px) { 263 | html { 264 | font-size: 33.3125px 265 | } 266 | } 267 | 268 | @media screen and (min-width: 600px) { 269 | html { 270 | font-size: 37.5px 271 | } 272 | } 273 | 274 | @media screen and (min-width: 768px) { 275 | html { 276 | font-size: 48px 277 | } 278 | } 279 | 280 | .m-ui-dial { 281 | position: relative; 282 | width: 499px; 283 | height: 499px; 284 | margin: 0 auto; 285 | background: url("../images/dial_bg.png") no-repeat center; 286 | background-size: 100% 287 | } 288 | 289 | .m-ui-dial .pointer { 290 | margin-top: -139px !important; 291 | background: url("../images/dial_pointer.png") no-repeat center; 292 | background-size: 100%; 293 | position: absolute; 294 | top: 50%; 295 | left: 50%; 296 | display: block; 297 | width: 150px; 298 | height: 238px; 299 | margin: -119px 0 0 -75px; 300 | -webkit-transform-origin: 75px 139px; 301 | transform-origin: 75px 139px 302 | } 303 | 304 | .m-ui-dial .btn { 305 | position: absolute; 306 | top: 64px; 307 | left: 0; 308 | display: block; 309 | width: 150px; 310 | height: 150px; 311 | border-radius: 75px 312 | } 313 | 314 | .m-ui-scratch { 315 | display: block; 316 | width: 300px; 317 | height: 172.5px; 318 | margin: 20px auto; 319 | background: no-repeat center; 320 | background-size: 100% 321 | } 322 | 323 | .m-ui-button { 324 | display: block; 325 | width: 160px; 326 | height: 32px; 327 | margin: 0 auto; 328 | text-align: center; 329 | line-height: 32px; 330 | font-size: 12px; 331 | color: #fff; 332 | background-color: #f94804; 333 | cursor: pointer; 334 | border-radius: 5px 335 | } 336 | 337 | .m-ui-tiger { 338 | position: relative; 339 | width: 640px; 340 | height: 432px; 341 | margin: 20px auto; 342 | background: url("../images/tiger_bg.png") no-repeat 343 | } 344 | 345 | .m-ui-tiger .toggle { 346 | position: absolute; 347 | top: 306px; 348 | left: 119px; 349 | display: block; 350 | width: 404px; 351 | height: 50px 352 | } 353 | 354 | .m-ui-tiger .item { 355 | position: absolute; 356 | top: 77px; 357 | left: 139px; 358 | display: block; 359 | width: 110px; 360 | height: 135px; 361 | overflow: hidden 362 | } 363 | 364 | .m-ui-tiger .item:nth-child(2) { 365 | left: 265px 366 | } 367 | 368 | .m-ui-tiger .item:nth-child(3) { 369 | left: 391px 370 | } 371 | 372 | .m-ui-tiger .roller { 373 | position: relative 374 | } 375 | 376 | .m-ui-tiger .roller li { 377 | width: 110px; 378 | height: 135px; 379 | overflow: hidden 380 | } 381 | 382 | .m-ui-tiger .roller li:last-child { 383 | position: absolute; 384 | top: 100%; 385 | left: 0 386 | } 387 | 388 | .m-ui-tiger .roller img { 389 | display: block; 390 | width: 110px; 391 | height: 135px 392 | } 393 | 394 | .m-ui-tiger .roller.fx-roll { 395 | -webkit-filter: blur(3px); 396 | filter: blur(3px); 397 | -webkit-animation: fx-roll 0.5s 0s infinite linear; 398 | animation: fx-roll 0.5s 0s infinite linear 399 | } 400 | 401 | .m-ui-tiger .roller.fx-bounce { 402 | -webkit-animation-duration: 0.3s; 403 | animation-duration: 0.3s 404 | } 405 | 406 | .m-lottery-scratch { 407 | position: relative; 408 | width: 16rem; 409 | min-height: 100%; 410 | margin: 0 auto; 411 | padding-top: 9rem; 412 | background: url("../images/scratch_page_bg.jpg") no-repeat #ff5e01; 413 | background-size: 100% auto 414 | } 415 | 416 | .m-lottery-scratch .game { 417 | position: relative; 418 | display: block; 419 | width: 10rem; 420 | margin: 0 auto; 421 | height: 5.75rem 422 | } 423 | 424 | .m-lottery-scratch .game canvas { 425 | position: absolute; 426 | top: 0; 427 | left: 0; 428 | display: block; 429 | width: 100%; 430 | height: 100%; 431 | background-size: 100% 100% 432 | } 433 | 434 | .m-lottery-scratch .game button { 435 | display: block; 436 | width: 5rem; 437 | height: 1.5rem; 438 | margin: 0 auto; 439 | text-align: center; 440 | line-height: 1.4rem; 441 | font-size: 0.7rem; 442 | color: #fff; 443 | background-color: #ff5e01; 444 | border: 0 none; 445 | cursor: pointer; 446 | border-radius: 0.3rem 447 | } 448 | 449 | .m-lottery-scratch .game button.z-dis { 450 | background-color: #bcbcbc 451 | } 452 | 453 | .m-lottery-scratch .game .ceiling { 454 | position: absolute; 455 | top: 0; 456 | left: 0; 457 | z-index: 3; 458 | width: 100%; 459 | height: 4rem; 460 | padding: 0.5rem 0 1.25rem; 461 | background: url("../images/scratch_ceiling.jpg") no-repeat; 462 | background-size: 100% 100%; 463 | overflow: hidden 464 | } 465 | 466 | .m-lottery-scratch .game .ceiling .tip { 467 | display: block; 468 | height: 2.5rem; 469 | line-height: 2.5rem; 470 | text-align: center; 471 | font-size: 0.8rem; 472 | color: #ff5e01; 473 | overflow: hidden 474 | } 475 | 476 | .m-lottery-scratch .des { 477 | margin-top: 2.35rem; 478 | padding: 0 1.1rem 0.5rem; 479 | line-height: 1.15rem; 480 | font-size: 0.6rem; 481 | color: #fff 482 | } 483 | 484 | .m-lottery-scratch .des dt { 485 | line-height: 1.2rem; 486 | font-size: 0.7rem; 487 | font-weight: 700 488 | } 489 | 490 | .m-lottery-dial { 491 | position: relative; 492 | width: 16rem; 493 | min-height: 100%; 494 | margin: 0 auto; 495 | padding-top: 7rem; 496 | background: url("../images/dial_page_bg.jpg") no-repeat #f94804; 497 | background-size: 100% auto 498 | } 499 | 500 | .m-lottery-dial .game-dial { 501 | position: relative; 502 | width: 12.5rem; 503 | height: 12.5rem; 504 | margin: 0 auto 505 | } 506 | 507 | .m-lottery-dial .game-dial .dial { 508 | width: 100%; 509 | height: 100%; 510 | background-size: 100% auto 511 | } 512 | 513 | .m-lottery-dial .game-dial .pointer { 514 | position: absolute; 515 | top: 50%; 516 | left: 50%; 517 | width: 3.75rem; 518 | height: 5.95rem; 519 | margin: -3.4rem 0 0 -1.875rem; 520 | background-size: 100% auto; 521 | -webkit-transform-origin: 1.875rem 3.4rem; 522 | transform-origin: 1.875rem 3.4rem 523 | } 524 | 525 | .m-lottery-dial .game-dial .pointer span { 526 | position: absolute; 527 | top: 1.6rem; 528 | left: 0; 529 | display: block; 530 | width: 3.75rem; 531 | height: 3.75rem; 532 | cursor: pointer 533 | } 534 | 535 | .m-lottery-dial .des { 536 | margin-top: 4.2rem; 537 | padding: 0 1.1rem 0.5rem; 538 | line-height: 1.15rem; 539 | font-size: 0.6rem; 540 | color: #fff 541 | } 542 | 543 | .m-lottery-dial .des dt { 544 | line-height: 1.2rem; 545 | font-size: 0.7rem; 546 | font-weight: 700 547 | } 548 | 549 | .m-lottery-tiger { 550 | position: relative; 551 | width: 16rem; 552 | min-height: 100%; 553 | margin: 0 auto; 554 | padding-top: 5.5rem; 555 | background: url("../images/tiger_page_bg.jpg") no-repeat #ffeec6; 556 | background-size: 100% auto 557 | } 558 | 559 | .m-lottery-tiger .game { 560 | position: relative 561 | } 562 | 563 | .m-lottery-tiger .game .back { 564 | width: 16rem; 565 | height: 10.8rem; 566 | background-size: 100%; 567 | background-repeat: no-repeat 568 | } 569 | 570 | .m-lottery-tiger .game .item { 571 | position: absolute; 572 | top: 1.925rem; 573 | width: 2.75rem; 574 | height: 3.375rem; 575 | overflow: hidden 576 | } 577 | 578 | .m-lottery-tiger .game .item:nth-child(1) { 579 | left: 3.475rem 580 | } 581 | 582 | .m-lottery-tiger .game .item:nth-child(2) { 583 | left: 6.625rem 584 | } 585 | 586 | .m-lottery-tiger .game .item:nth-child(3) { 587 | left: 9.775rem 588 | } 589 | 590 | .m-lottery-tiger .game .item .roller { 591 | position: relative 592 | } 593 | 594 | .m-lottery-tiger .game .item .roller li.z-last { 595 | position: absolute; 596 | top: 100%; 597 | left: 0 598 | } 599 | 600 | .m-lottery-tiger .game .item .roller.fx-bounce { 601 | -webkit-animation-duration: 0.3s; 602 | animation-duration: 0.3s 603 | } 604 | 605 | .m-lottery-tiger .game .item .roller.fx-roll { 606 | -webkit-filter: blur(3px); 607 | filter: blur(3px); 608 | -webkit-animation: fx-roll 0.3s 0s infinite linear; 609 | animation: fx-roll 0.3s 0s infinite linear 610 | } 611 | 612 | .m-lottery-tiger .game .item img { 613 | display: block; 614 | width: 2.75rem; 615 | height: 3.375rem 616 | } 617 | 618 | .m-lottery-tiger .game .btn { 619 | position: absolute; 620 | left: 50%; 621 | top: 7.65rem; 622 | width: 10.1rem; 623 | height: 1.25rem; 624 | margin-left: -5.05rem; 625 | background-size: 100%; 626 | background-repeat: no-repeat 627 | } 628 | 629 | .m-lottery-tiger .game .btn span { 630 | display: block; 631 | width: 100%; 632 | height: 100%; 633 | cursor: pointer 634 | } 635 | 636 | .m-lottery-tiger .des { 637 | padding: 0 1.1rem 0.5rem; 638 | line-height: 1.15rem; 639 | font-size: 0.6rem; 640 | color: #b26722 641 | } 642 | 643 | .m-lottery-tiger .des dt { 644 | line-height: 1.2rem; 645 | font-size: 0.7rem; 646 | font-weight: 700 647 | } 648 | 649 | @-webkit-keyframes fx-roll { 650 | 0% { 651 | -webkit-transform: translate3d(0, 0, 0); 652 | transform: translate3d(0, 0, 0) 653 | } 654 | 100% { 655 | -webkit-transform: translate3d(0, -100%, 0); 656 | transform: translate3d(0, -100%, 0) 657 | } 658 | } 659 | 660 | @keyframes fx-roll { 661 | 0% { 662 | -webkit-transform: translate3d(0, 0, 0); 663 | transform: translate3d(0, 0, 0) 664 | } 665 | 100% { 666 | -webkit-transform: translate3d(0, -100%, 0); 667 | transform: translate3d(0, -100%, 0) 668 | } 669 | } 670 | 671 | .fx-bounce { 672 | -webkit-animation-name: bounce; 673 | animation-name: bounce; 674 | -webkit-transform-origin: center bottom; 675 | transform-origin: center bottom 676 | } 677 | 678 | @-webkit-keyframes bounce { 679 | 0%, 20%, 53%, 80%, 100% { 680 | -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); 681 | animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); 682 | -webkit-transform: translate3d(0, 0, 0); 683 | transform: translate3d(0, 0, 0) 684 | } 685 | 40%, 43% { 686 | -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); 687 | animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); 688 | -webkit-transform: translate3d(0, -30px, 0); 689 | transform: translate3d(0, -30px, 0) 690 | } 691 | 70% { 692 | -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); 693 | animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); 694 | -webkit-transform: translate3d(0, -15px, 0); 695 | transform: translate3d(0, -15px, 0) 696 | } 697 | 90% { 698 | -webkit-transform: translate3d(0, -4px, 0); 699 | transform: translate3d(0, -4px, 0) 700 | } 701 | } 702 | 703 | @keyframes bounce { 704 | 0%, 20%, 53%, 80%, 100% { 705 | -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); 706 | animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); 707 | -webkit-transform: translate3d(0, 0, 0); 708 | transform: translate3d(0, 0, 0) 709 | } 710 | 40%, 43% { 711 | -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); 712 | animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); 713 | -webkit-transform: translate3d(0, -30px, 0); 714 | transform: translate3d(0, -30px, 0) 715 | } 716 | 70% { 717 | -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); 718 | animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); 719 | -webkit-transform: translate3d(0, -15px, 0); 720 | transform: translate3d(0, -15px, 0) 721 | } 722 | 90% { 723 | -webkit-transform: translate3d(0, -4px, 0); 724 | transform: translate3d(0, -4px, 0) 725 | } 726 | } 727 | --------------------------------------------------------------------------------