UNPKG

9.04 kBJavaScriptView Raw
1var LotteryWheel = function LotteryWheel(container, params) {
2 /* ----------------------
3 Model
4 ---------------------- */
5 var defaults = {
6 // 间隔
7 spacing: 0,
8 // 数据
9 data: [], // [{text: '', icon: '', font: '', fontTop...同数据默认值}]
10 // 数据默认值
11 fontFamily: 'Arial',
12 fontSize: 13,
13 fontTop: 28,
14 fontFillStyle: '#ef694f',
15
16 bgFillStyle: '#ffdf7d',
17 bgStrokeStyle: '#fa8b6e',
18 bgLineWidth: 1,
19
20 iconWidth: 42,
21 iconHeight: 42,
22 iconTop: 42,
23
24 around: 6, // 转6圈
25 // 创建外层容器
26 wrapperClass: 'lotterywheel-wrapper',
27 // 保存
28 suffix: 'image/png',
29 quality: 0.92
30
31 /* callbacks
32 onTransitionEnd:function (LotteryWheel) // 动画结束后回调
33 onPlayAnimationEnd:function (LotteryWheel) // 播放动画结束后回调
34 */
35 };
36 params = params || {};
37 for (var def in defaults) {
38 if (params[def] === undefined) {
39 params[def] = defaults[def];
40 }
41 }
42 var s = this;
43 s.params = params;
44 // Canvas
45 s.canvas = typeof container === 'string' ? document.querySelector(container) : container;
46 if (!s.canvas) {
47 console.log('SeedsUI Error : Lottery container不存在,请检查页面中是否有此元素');
48 return;
49 }
50 s.width = s.canvas.width || 300;
51 s.height = s.canvas.height || 300;
52 // Wrapper, 包裹canvas, 用于在canvas同级创建文字和图片
53 s.wrapper = null;
54 s.createWrapper = function () {
55 s.wrapper = document.createElement('div');
56 s.wrapper.setAttribute('class', s.params.wrapperClass + ' animated');
57 s.wrapper.style.width = s.width + 'px';
58 s.wrapper.style.height = s.height + 'px';
59 s.canvas.parentNode.replaceChild(s.wrapper, s.canvas);
60 s.wrapper.appendChild(s.canvas);
61 };
62 s.create = function () {
63 s.createWrapper();
64 };
65 s.create();
66 s.ctx = s.canvas.getContext('2d');
67 /* ----------------------
68 Method
69 ---------------------- */
70 // 清除
71 s.clear = function () {
72 s.ctx.clearRect(0, 0, s.width, s.height);
73 };
74 // 获取设备缩放比率
75 s.getPixelRatio = function () {
76 var context = s.ctx;
77 var backingStore = context.backingStorePixelRatio || context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || context.backingStorePixelRatio || 1;
78 return (window.devicePixelRatio || 1) / backingStore;
79 };
80 // 封装扇形函数, 参数: x坐标、y坐标、半径、起始角、结束角、是否逆时针
81 s.sector = function (x, y, radius, sAngle, eAngle, counterclockwise) {
82 // 开始一条新路径
83 s.ctx.beginPath();
84 // 起始点移动到圆心
85 s.ctx.moveTo(x, y);
86 // 绘制圆弧
87 s.ctx.arc(x, y, radius, sAngle, eAngle, counterclockwise);
88 // 闭合路径
89 s.ctx.closePath();
90 };
91 s.slotDeg = 0; // 一槽的度数
92 s.rad = 0; // 一度的弧度
93 s.slotRad = 0; // 一槽的弧度
94 // 更新一槽的弧度
95 s.updateAngle = function () {
96 s.slotDeg = 360 / s.params.data.length; // 一槽的度数
97 s.rad = Math.PI / 180; // 一度的弧度
98 s.slotRad = s.rad * s.slotDeg; // 一槽的弧度
99 };
100 // 绘制背景-单个扇形
101 s.drawCanvas = function (item, index) {
102 var ratio = s.getPixelRatio();
103 // 计算圆的半径与位置
104 var xy = Math.min(s.width || 300, s.height || 300) / 2;
105 var r = xy - (s.params.spacing || 0);
106 // 设置背景样式
107 s.ctx.strokeStyle = item.bgStrokeStyle || s.params.bgStrokeStyle;
108 s.ctx.fillStyle = item.bgFillStyle || s.params.bgFillStyle;
109 s.ctx.lineWidth = item.bgLineWidth || s.params.bgLineWidth;
110
111 // 计算开始和结束角, 使开始位置从正上方开始
112 var startAngel = s.rad * -90 - s.slotRad / 2;
113 var endAngel = startAngel + s.slotRad;
114
115 // 设置中心点与旋转弧度, 旋转完再绘
116 s.ctx.translate(xy, xy);
117 if (index === 0) s.ctx.rotate(s.slotRad / 2);
118 if (index !== 0) s.ctx.rotate(s.slotRad);
119 s.ctx.translate(-xy, -xy);
120
121 // 绘制前, 保存状态, 防止绘制污染
122 // s.ctx.save()
123
124 // 绘背景
125 s.sector(xy, xy, r, startAngel, endAngel);
126 s.ctx.stroke();
127 s.ctx.fill();
128
129 // 绘文字
130 var fontSize = (item.fontSize || s.params.fontSize) * ratio;
131 var fontFamily = item.fontFamily || s.params.fontFamily;
132 var fontTop = (item.fontTop || s.params.fontTop) * ratio;
133 s.ctx.font = fontSize + 'px ' + fontFamily;
134 s.ctx.textAlign = 'center';
135 s.ctx.textBaseline = 'middle';
136 s.ctx.fillStyle = item.fontFillStyle || s.params.fontFillStyle;
137 s.ctx.fillText(item.text || '', xy, fontTop);
138
139 // 绘图片
140 var img = s.imgs[index];
141 if (img.getAttribute('data-complete') === '1') {
142 var imgTop = (item.iconTop || s.params.iconTop) * ratio;
143 var imgWidth = (item.iconWidth || s.params.iconWidth) * ratio;
144 var imgHeight = (item.iconHeight || s.params.iconHeight) * ratio;
145 s.ctx.drawImage(img, xy - imgWidth / 2, imgTop, imgWidth, imgHeight);
146 }
147
148 // 还原绘前状态
149 // s.ctx.restore()
150 };
151 // 防止失真, 在高分屏上, 所以要放大2倍绘制, 再缩小2倍
152 s.update = function () {
153 // 放大2倍
154 var ratio = s.getPixelRatio();
155 s.width = s.width * ratio;
156 s.height = s.height * ratio;
157 s.canvas.width = s.width;
158 s.canvas.height = s.height;
159 s.ctx.width = s.width;
160 s.ctx.height = s.height;
161 // 缩小2倍
162 s.canvas.style.WebkitTransform = 'scale(' + 1 / ratio + ')';
163 s.canvas.style.WebkitTransformOrigin = 'left top';
164 // 更新一槽的弧度
165 s.updateAngle();
166 };
167 s.update();
168 /* ----------------------
169 Imgs, 先用img标签加载图片, 加载完成后再绘制canvas, 目的是为了解决跨域的问题
170 ---------------------- */
171 // 绘制图片, 解决canvas跨域的问题
172 s.imgs = [];
173 s.initImgs = function () {
174 if (!s.params.data || !s.params.data.length) return;
175 s.imgs = [];
176 for (var i = 0; i < s.params.data.length; i++) {
177 var item = s.params.data[i];
178 var img = document.createElement('img');
179 img.src = item.icon;
180 img.style.display = 'none';
181 document.body.appendChild(img);
182 img.addEventListener('load', s.onIconLoad, false);
183 img.addEventListener('error', s.onIconError, false);
184 s.imgs.push(img);
185 }
186 };
187 // 全部加载完成后, 再绘制图片
188 s.imgsCompleteDraw = function () {
189 var complete = s.imgs.filter(function (img) {
190 return img.getAttribute('data-complete');
191 });
192 if (complete.length === s.imgs.length) {
193 console.log('图片加载完成, 开始绘制');
194 s.draw();
195 }
196 };
197 s.onIconLoad = function (e) {
198 e.target.setAttribute('data-complete', '1');
199 // 全部加载完成后, 再绘制图片
200 s.imgsCompleteDraw();
201 };
202 s.onIconError = function (e) {
203 e.target.setAttribute('data-complete', '-1');
204 // 全部加载完成后, 再绘制图片
205 s.imgsCompleteDraw();
206 };
207 /* ----------------------
208 Events
209 ---------------------- */
210 s.events = function (detach) {
211 var action = detach ? 'removeEventListener' : 'addEventListener';
212 if (s.wrapper) s.wrapper[action]('webkitTransitionEnd', s.onTransitionEnd, false);
213 };
214 s.detach = function (event) {
215 s.events(true);
216 };
217 s.attach = function (event) {
218 s.events();
219 };
220 s.onTransitionEnd = function (e) {
221 s.event = e;
222 // 动画转动完成回调
223 if (s.params.onTransitionEnd) s.params.onTransitionEnd(s);
224 // 转盘部分转动完成回调
225 var target = e.target;
226 if (target.classList.contains(s.params.wrapperClass)) {
227 s.playing = false;
228 if (s.params.onPlayAnimationEnd) s.params.onPlayAnimationEnd(s);
229 }
230 };
231 /* ----------------------
232 Main
233 ---------------------- */
234 // 绘制canvas
235 s.draw = function () {
236 if (!s.params.data || !s.params.data.length) return;
237 s.reset();
238 s.clear();
239 // 保存初始状态
240 s.ctx.save();
241 for (var i = 0; i < s.params.data.length; i++) {
242 s.drawCanvas(s.params.data[i], i);
243 }
244 };
245 // 复位
246 s.reset = function () {
247 // 更新一槽的弧度
248 s.updateAngle();
249 // 去除动画, 旋转角度复位
250 s.wrapper.classList.remove('animated');
251 s.wrapper.style.WebkitTransform = 'rotate(0deg)';
252 // 还原初始状态
253 if (s.ctx) s.ctx.restore();
254 };
255 // 转动转盘
256 s.playing = false;
257 s.play = function (count, onPlayAnimationEnd) {
258 if (s.playing) return;
259 s.playing = true;
260 if (!s.params.data || !s.params.data.length) return;
261 var baseRotate = (s.params.around || 6) * 360; // 转固定几圈, 并指向奖品的正中间
262 var rotate = (count + 1) * s.slotDeg - s.slotDeg / 2;
263
264 s.wrapper.classList.add('animated');
265 s.wrapper.style.WebkitTransform = 'rotate(' + (baseRotate - rotate) + 'deg)';
266
267 // Callback
268 if (onPlayAnimationEnd) s.params.onPlayAnimationEnd = onPlayAnimationEnd;
269 };
270 // 主函数
271 s.init = function () {
272 s.initImgs();
273 s.attach();
274 };
275
276 s.init();
277};
278
279export default LotteryWheel;
\No newline at end of file