├── img ├── bg.jpg ├── cd.png ├── pre.png ├── bg_top.jpg ├── lrc_bg.png ├── next.png ├── pause.png ├── play.png ├── poster.jpg ├── bg_list.jpg ├── control.png ├── favicon.ico ├── bg_bottom.jpg ├── icon_love.png ├── icon_love_active.png └── poster │ ├── 0-origin.jpg │ ├── 1-origin.jpg │ ├── 10-origin.jpg │ ├── 11-origin.jpg │ ├── 12-origin.jpg │ ├── 13-origin.jpg │ ├── 14-origin.jpg │ ├── 15-origin.jpg │ ├── 16-origin.jpg │ ├── 2-origin.jpg │ ├── 3-origin.jpg │ ├── 4-origin.jpg │ ├── 5-origin.jpg │ ├── 6-origin.jpg │ ├── 7-origin.jpg │ ├── 8-origin.jpg │ ├── 9-origin.jpg │ ├── 0-thumbnail.jpg │ ├── 1-thumbnail.jpg │ ├── 12-origin .jpg │ ├── 12-origin-.jpg │ ├── 2-thumbnail.jpg │ ├── 3-thumbnail.jpg │ ├── 4-thumbnail.jpg │ ├── 5-thumbnail.jpg │ ├── 6-thumbnail.jpg │ ├── 7-thumbnail.jpg │ ├── 8-thumbnail.jpg │ ├── 9-thumbnail.jpg │ ├── 10-thumbnail.jpg │ ├── 11-thumbnail.jpg │ ├── 12-thumbnail.jpg │ ├── 13-thumbnail.jpg │ ├── 14-thumbnail.jpg │ ├── 15-thumbnail.jpg │ └── 16-thumbnail.jpg ├── fonts ├── icomoon.eot ├── icomoon.ttf ├── icomoon.woff └── icomoon.svg ├── LICENSE ├── css ├── common.css ├── normalize.css └── index.css ├── index.html ├── README.md ├── lib ├── jquery.touchSwipe.min.js └── iscroll.js ├── data └── data.json └── js └── main.js /img/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/bg.jpg -------------------------------------------------------------------------------- /img/cd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/cd.png -------------------------------------------------------------------------------- /img/pre.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/pre.png -------------------------------------------------------------------------------- /img/bg_top.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/bg_top.jpg -------------------------------------------------------------------------------- /img/lrc_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/lrc_bg.png -------------------------------------------------------------------------------- /img/next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/next.png -------------------------------------------------------------------------------- /img/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/pause.png -------------------------------------------------------------------------------- /img/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/play.png -------------------------------------------------------------------------------- /img/poster.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster.jpg -------------------------------------------------------------------------------- /img/bg_list.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/bg_list.jpg -------------------------------------------------------------------------------- /img/control.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/control.png -------------------------------------------------------------------------------- /img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/favicon.ico -------------------------------------------------------------------------------- /fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/fonts/icomoon.eot -------------------------------------------------------------------------------- /fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/fonts/icomoon.ttf -------------------------------------------------------------------------------- /fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/fonts/icomoon.woff -------------------------------------------------------------------------------- /img/bg_bottom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/bg_bottom.jpg -------------------------------------------------------------------------------- /img/icon_love.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/icon_love.png -------------------------------------------------------------------------------- /img/icon_love_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/icon_love_active.png -------------------------------------------------------------------------------- /img/poster/0-origin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/0-origin.jpg -------------------------------------------------------------------------------- /img/poster/1-origin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/1-origin.jpg -------------------------------------------------------------------------------- /img/poster/10-origin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/10-origin.jpg -------------------------------------------------------------------------------- /img/poster/11-origin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/11-origin.jpg -------------------------------------------------------------------------------- /img/poster/12-origin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/12-origin.jpg -------------------------------------------------------------------------------- /img/poster/13-origin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/13-origin.jpg -------------------------------------------------------------------------------- /img/poster/14-origin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/14-origin.jpg -------------------------------------------------------------------------------- /img/poster/15-origin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/15-origin.jpg -------------------------------------------------------------------------------- /img/poster/16-origin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/16-origin.jpg -------------------------------------------------------------------------------- /img/poster/2-origin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/2-origin.jpg -------------------------------------------------------------------------------- /img/poster/3-origin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/3-origin.jpg -------------------------------------------------------------------------------- /img/poster/4-origin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/4-origin.jpg -------------------------------------------------------------------------------- /img/poster/5-origin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/5-origin.jpg -------------------------------------------------------------------------------- /img/poster/6-origin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/6-origin.jpg -------------------------------------------------------------------------------- /img/poster/7-origin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/7-origin.jpg -------------------------------------------------------------------------------- /img/poster/8-origin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/8-origin.jpg -------------------------------------------------------------------------------- /img/poster/9-origin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/9-origin.jpg -------------------------------------------------------------------------------- /img/poster/0-thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/0-thumbnail.jpg -------------------------------------------------------------------------------- /img/poster/1-thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/1-thumbnail.jpg -------------------------------------------------------------------------------- /img/poster/12-origin .jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/12-origin .jpg -------------------------------------------------------------------------------- /img/poster/12-origin-.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/12-origin-.jpg -------------------------------------------------------------------------------- /img/poster/2-thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/2-thumbnail.jpg -------------------------------------------------------------------------------- /img/poster/3-thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/3-thumbnail.jpg -------------------------------------------------------------------------------- /img/poster/4-thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/4-thumbnail.jpg -------------------------------------------------------------------------------- /img/poster/5-thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/5-thumbnail.jpg -------------------------------------------------------------------------------- /img/poster/6-thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/6-thumbnail.jpg -------------------------------------------------------------------------------- /img/poster/7-thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/7-thumbnail.jpg -------------------------------------------------------------------------------- /img/poster/8-thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/8-thumbnail.jpg -------------------------------------------------------------------------------- /img/poster/9-thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/9-thumbnail.jpg -------------------------------------------------------------------------------- /img/poster/10-thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/10-thumbnail.jpg -------------------------------------------------------------------------------- /img/poster/11-thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/11-thumbnail.jpg -------------------------------------------------------------------------------- /img/poster/12-thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/12-thumbnail.jpg -------------------------------------------------------------------------------- /img/poster/13-thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/13-thumbnail.jpg -------------------------------------------------------------------------------- /img/poster/14-thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/14-thumbnail.jpg -------------------------------------------------------------------------------- /img/poster/15-thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/15-thumbnail.jpg -------------------------------------------------------------------------------- /img/poster/16-thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex1504/jquery-mobile-player/HEAD/img/poster/16-thumbnail.jpg -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 alex1504 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /css/common.css: -------------------------------------------------------------------------------- 1 | /* 字体库样式 */ 2 | @font-face { 3 | font-family: 'icomoon'; 4 | src: url('../fonts/icomoon.eot?m26i4l'); 5 | src: url('../fonts/icomoon.eot?m26i4l#iefix') format('embedded-opentype'), 6 | url('../fonts/icomoon.ttf?m26i4l') format('truetype'), 7 | url('../fonts/icomoon.woff?m26i4l') format('woff'), 8 | url('../fonts/icomoon.svg?m26i4l#icomoon') format('svg'); 9 | font-weight: normal; 10 | font-style: normal; 11 | } 12 | 13 | [class^="icon-"], [class*=" icon-"] { 14 | /* use !important to prevent issues with browser extensions that change fonts */ 15 | font-family: 'icomoon' !important; 16 | speak: none; 17 | font-style: normal; 18 | font-weight: normal; 19 | font-variant: normal; 20 | text-transform: none; 21 | line-height: 1; 22 | 23 | /* Better Font Rendering =========== */ 24 | -webkit-font-smoothing: antialiased; 25 | -moz-osx-font-smoothing: grayscale; 26 | } 27 | 28 | .icon-user:before { 29 | content: "\e971"; 30 | } 31 | .icon-search:before { 32 | content: "\e986"; 33 | } 34 | .icon-cog:before { 35 | content: "\e994"; 36 | } 37 | .icon-menu:before { 38 | content: "\e9bd"; 39 | } 40 | .icon-cross:before { 41 | content: "\ea0f"; 42 | } 43 | .icon-volume-high:before { 44 | content: "\ea26"; 45 | } 46 | .icon-volume-mute:before { 47 | content: "\ea29"; 48 | } 49 | .icon-loop:before { 50 | content: "\ea2d"; 51 | } 52 | .icon-random:before { 53 | content: "\ea30"; 54 | } 55 | .icon-arrow-left:before { 56 | content: "\ea40"; 57 | } 58 | 59 | 60 | /* END字体库样式 */ 61 | 62 | 63 | /* 公共样式 */ 64 | html, body{ 65 | height: 100%; 66 | background: url(../img/bg.jpg) no-repeat center center; 67 | background-size: cover; 68 | overflow-x: hidden; 69 | } 70 | .page{ 71 | height: 100%; 72 | background: url(../img/bg.jpg) no-repeat center center; 73 | background-size: cover; 74 | color:#fff; 75 | font-size: 0.3rem; 76 | font-family: "Microsoft YaHei","微软雅黑","sans-serif"; 77 | } 78 | .page i{ 79 | text-align: center; 80 | } 81 | .btm-line{ 82 | border-bottom: 1px solid rgba(255, 255, 255, 0.5); 83 | } 84 | .show{ 85 | display: block !important; 86 | } 87 | .hide{ 88 | opacity:0 !important; 89 | } 90 | .disno{ 91 | display: none !important; 92 | } 93 | /* 公共样式 */ 94 | -------------------------------------------------------------------------------- /fonts/icomoon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | HTML5 Player 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 | 62 | 63 | 64 | 70 | 71 | 72 |
73 | 78 |
79 | 80 |
81 | 82 | 83 |
84 |
85 |
    86 | 102 | 103 |
104 |
105 |
106 |
    107 | 还没有喜爱的歌曲,赶紧在列表中添加吧! 108 |
109 |
110 |
111 |
    112 | 搜索功能暂未实现 113 |
114 |
115 |
116 | 117 | 118 | 126 | 127 |
128 | 129 | 130 |
131 | 132 |
133 | 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 |
  • 0:00
  • 194 |
  • 0:00
  • 195 |
196 |
197 |
198 | 199 | 200 | 201 |
202 |
203 | 204 | 205 | 206 |
207 |
208 | 209 |
210 | 211 |
212 | 213 | 214 |
215 |
216 |

项目目的

217 |

此项目纯属个人爱好,用于研究H5音频相关API和JS模板渲染及动态加载,其中涉及handlebar模板渲染、下拉动态渲染、H5本地存储、移动端rem自适应布局等,其中仍有很多不完善之处,欢迎交流和指正。

218 |
219 |
220 | 221 | 222 | 223 | 224 | -------------------------------------------------------------------------------- /css/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v4.1.1 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | 4 | /** 5 | * 1. Change the default font family in all browsers (opinionated). 6 | * 2. Prevent adjustments of font size after orientation changes in IE and iOS. 7 | */ 8 | 9 | html { 10 | font-family: sans-serif; 11 | /* 1 */ 12 | -ms-text-size-adjust: 100%; 13 | /* 2 */ 14 | -webkit-text-size-adjust: 100%; 15 | /* 2 */ 16 | font-size: 100px; 17 | } 18 | 19 | 20 | /** 21 | * Remove the margin in all browsers (opinionated). 22 | */ 23 | 24 | body { 25 | margin: 0; 26 | } 27 | 28 | 29 | /* HTML5 display definitions 30 | ========================================================================== */ 31 | 32 | 33 | /** 34 | * Add the correct display in IE 9-. 35 | * 1. Add the correct display in Edge, IE, and Firefox. 36 | * 2. Add the correct display in IE. 37 | */ 38 | 39 | article, 40 | aside, 41 | details, 42 | 43 | /* 1 */ 44 | 45 | figcaption, 46 | figure, 47 | footer, 48 | header, 49 | main, 50 | 51 | /* 2 */ 52 | 53 | menu, 54 | nav, 55 | section, 56 | summary { 57 | /* 1 */ 58 | display: block; 59 | } 60 | 61 | 62 | /** 63 | * Add the correct display in IE 9-. 64 | */ 65 | 66 | audio, 67 | canvas, 68 | progress, 69 | video { 70 | display: inline-block; 71 | } 72 | 73 | 74 | /** 75 | * Add the correct display in iOS 4-7. 76 | */ 77 | 78 | audio:not([controls]) { 79 | display: none; 80 | height: 0; 81 | } 82 | 83 | 84 | /** 85 | * Add the correct vertical alignment in Chrome, Firefox, and Opera. 86 | */ 87 | 88 | progress { 89 | vertical-align: baseline; 90 | } 91 | 92 | 93 | /** 94 | * Add the correct display in IE 10-. 95 | * 1. Add the correct display in IE. 96 | */ 97 | 98 | template, 99 | 100 | /* 1 */ 101 | 102 | [hidden] { 103 | display: none; 104 | } 105 | 106 | 107 | /* Links 108 | ========================================================================== */ 109 | 110 | 111 | /** 112 | * 1. Remove the gray background on active links in IE 10. 113 | * 2. Remove gaps in links underline in iOS 8+ and Safari 8+. 114 | */ 115 | 116 | a { 117 | background-color: transparent; 118 | /* 1 */ 119 | -webkit-text-decoration-skip: objects; 120 | /* 2 */ 121 | } 122 | 123 | 124 | /** 125 | * Remove the outline on focused links when they are also active or hovered 126 | * in all browsers (opinionated). 127 | */ 128 | 129 | a:active, 130 | a:hover { 131 | outline-width: 0; 132 | } 133 | 134 | 135 | /* Text-level semantics 136 | ========================================================================== */ 137 | 138 | 139 | /** 140 | * 1. Remove the bottom border in Firefox 39-. 141 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 142 | */ 143 | 144 | abbr[title] { 145 | border-bottom: none; 146 | /* 1 */ 147 | text-decoration: underline; 148 | /* 2 */ 149 | text-decoration: underline dotted; 150 | /* 2 */ 151 | } 152 | 153 | 154 | /** 155 | * Prevent the duplicate application of `bolder` by the next rule in Safari 6. 156 | */ 157 | 158 | b, 159 | strong { 160 | font-weight: inherit; 161 | } 162 | 163 | 164 | /** 165 | * Add the correct font weight in Chrome, Edge, and Safari. 166 | */ 167 | 168 | b, 169 | strong { 170 | font-weight: bolder; 171 | } 172 | 173 | 174 | /** 175 | * Add the correct font style in Android 4.3-. 176 | */ 177 | 178 | dfn { 179 | font-style: italic; 180 | } 181 | 182 | 183 | /** 184 | * Correct the font size and margin on `h1` elements within `section` and 185 | * `article` contexts in Chrome, Firefox, and Safari. 186 | */ 187 | 188 | h1 { 189 | font-size: 2em; 190 | margin: 0.67em 0; 191 | } 192 | 193 | 194 | /** 195 | * Add the correct background and color in IE 9-. 196 | */ 197 | 198 | mark { 199 | background-color: #ff0; 200 | color: #000; 201 | } 202 | 203 | 204 | /** 205 | * Add the correct font size in all browsers. 206 | */ 207 | 208 | small { 209 | font-size: 80%; 210 | } 211 | 212 | 213 | /** 214 | * Prevent `sub` and `sup` elements from affecting the line height in 215 | * all browsers. 216 | */ 217 | 218 | sub, 219 | sup { 220 | font-size: 75%; 221 | line-height: 0; 222 | vertical-align: baseline; 223 | } 224 | 225 | sub { 226 | bottom: -0.25em; 227 | } 228 | 229 | sup { 230 | top: -0.5em; 231 | } 232 | 233 | 234 | /* Embedded content 235 | ========================================================================== */ 236 | 237 | 238 | /** 239 | * Remove the border on images inside links in IE 10-. 240 | */ 241 | 242 | img { 243 | border-style: none; 244 | max-width: 100%; 245 | } 246 | 247 | 248 | /** 249 | * Hide the overflow in IE. 250 | */ 251 | 252 | svg:not(:root) { 253 | overflow: hidden; 254 | } 255 | 256 | 257 | /* Grouping content 258 | ========================================================================== */ 259 | 260 | 261 | /** 262 | * 1. Correct the inheritance and scaling of font size in all browsers. 263 | * 2. Correct the odd `em` font sizing in all browsers. 264 | */ 265 | 266 | code, 267 | kbd, 268 | pre, 269 | samp { 270 | font-family: monospace, monospace; 271 | /* 1 */ 272 | font-size: 1em; 273 | /* 2 */ 274 | } 275 | 276 | 277 | /** 278 | * Add the correct margin in IE 8. 279 | */ 280 | 281 | figure { 282 | margin: 1em 40px; 283 | } 284 | 285 | 286 | /** 287 | * 1. Add the correct box sizing in Firefox. 288 | * 2. Show the overflow in Edge and IE. 289 | */ 290 | 291 | hr { 292 | box-sizing: content-box; 293 | /* 1 */ 294 | height: 0; 295 | /* 1 */ 296 | overflow: visible; 297 | /* 2 */ 298 | } 299 | 300 | 301 | /* Forms 302 | ========================================================================== */ 303 | 304 | 305 | /** 306 | * 1. Change font properties to `inherit` in all browsers (opinionated). 307 | * 2. Remove the margin in Firefox and Safari. 308 | */ 309 | 310 | button, 311 | input, 312 | select, 313 | textarea { 314 | font: inherit; 315 | /* 1 */ 316 | margin: 0; 317 | /* 2 */ 318 | outline: medium; 319 | } 320 | 321 | 322 | /** 323 | * Restore the font weight unset by the previous rule. 324 | */ 325 | 326 | optgroup { 327 | font-weight: bold; 328 | } 329 | 330 | 331 | /** 332 | * Show the overflow in IE. 333 | * 1. Show the overflow in Edge. 334 | */ 335 | 336 | button, 337 | input { 338 | /* 1 */ 339 | overflow: visible; 340 | } 341 | 342 | 343 | /** 344 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 345 | * 1. Remove the inheritance of text transform in Firefox. 346 | */ 347 | 348 | button, 349 | select { 350 | /* 1 */ 351 | text-transform: none; 352 | } 353 | 354 | 355 | /** 356 | * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` 357 | * controls in Android 4. 358 | * 2. Correct the inability to style clickable types in iOS and Safari. 359 | */ 360 | 361 | button, 362 | html [type="button"], 363 | 364 | /* 1 */ 365 | 366 | [type="reset"], 367 | [type="submit"] { 368 | -webkit-appearance: button; 369 | /* 2 */ 370 | } 371 | 372 | 373 | /** 374 | * Remove the inner border and padding in Firefox. 375 | */ 376 | 377 | button::-moz-focus-inner, 378 | [type="button"]::-moz-focus-inner, 379 | [type="reset"]::-moz-focus-inner, 380 | [type="submit"]::-moz-focus-inner { 381 | border-style: none; 382 | padding: 0; 383 | } 384 | 385 | 386 | /** 387 | * Restore the focus styles unset by the previous rule. 388 | */ 389 | 390 | button:-moz-focusring, 391 | [type="button"]:-moz-focusring, 392 | [type="reset"]:-moz-focusring, 393 | [type="submit"]:-moz-focusring { 394 | outline: 1px dotted ButtonText; 395 | } 396 | 397 | 398 | /** 399 | * Change the border, margin, and padding in all browsers (opinionated). 400 | */ 401 | 402 | fieldset { 403 | border: 1px solid #c0c0c0; 404 | margin: 0 2px; 405 | padding: 0.35em 0.625em 0.75em; 406 | } 407 | 408 | 409 | /** 410 | * 1. Correct the text wrapping in Edge and IE. 411 | * 2. Correct the color inheritance from `fieldset` elements in IE. 412 | * 3. Remove the padding so developers are not caught out when they zero out 413 | * `fieldset` elements in all browsers. 414 | */ 415 | 416 | legend { 417 | box-sizing: border-box; 418 | /* 1 */ 419 | color: inherit; 420 | /* 2 */ 421 | display: table; 422 | /* 1 */ 423 | max-width: 100%; 424 | /* 1 */ 425 | padding: 0; 426 | /* 3 */ 427 | white-space: normal; 428 | /* 1 */ 429 | } 430 | 431 | 432 | /** 433 | * Remove the default vertical scrollbar in IE. 434 | */ 435 | 436 | textarea { 437 | overflow: auto; 438 | } 439 | 440 | 441 | /** 442 | * 1. Add the correct box sizing in IE 10-. 443 | * 2. Remove the padding in IE 10-. 444 | */ 445 | 446 | [type="checkbox"], 447 | [type="radio"] { 448 | box-sizing: border-box; 449 | /* 1 */ 450 | padding: 0; 451 | /* 2 */ 452 | } 453 | 454 | 455 | /** 456 | * Correct the cursor style of increment and decrement buttons in Chrome. 457 | */ 458 | 459 | [type="number"]::-webkit-inner-spin-button, 460 | [type="number"]::-webkit-outer-spin-button { 461 | height: auto; 462 | } 463 | 464 | 465 | /** 466 | * 1. Correct the odd appearance in Chrome and Safari. 467 | * 2. Correct the outline style in Safari. 468 | */ 469 | 470 | [type="search"] { 471 | -webkit-appearance: textfield; 472 | /* 1 */ 473 | outline-offset: -2px; 474 | /* 2 */ 475 | } 476 | 477 | 478 | /** 479 | * Remove the inner padding and cancel buttons in Chrome and Safari on OS X. 480 | */ 481 | 482 | [type="search"]::-webkit-search-cancel-button, 483 | [type="search"]::-webkit-search-decoration { 484 | -webkit-appearance: none; 485 | } 486 | 487 | 488 | /** 489 | * Correct the text style of placeholders in Chrome, Edge, and Safari. 490 | */ 491 | 492 | ::-webkit-input-placeholder { 493 | color: inherit; 494 | opacity: 0.54; 495 | } 496 | 497 | 498 | /** 499 | * 1. Correct the inability to style clickable types in iOS and Safari. 500 | * 2. Change font properties to `inherit` in Safari. 501 | */ 502 | 503 | ::-webkit-file-upload-button { 504 | -webkit-appearance: button; 505 | /* 1 */ 506 | font: inherit; 507 | /* 2 */ 508 | } 509 | 510 | ul { 511 | list-style: none; 512 | padding: 0; 513 | margin: 0; 514 | } 515 | 516 | h2 { 517 | margin: 0; 518 | } 519 | 520 | ` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 项目说明 2 | ![UI图](http://huzerui.com/blog/img/post/2016-10-30-how-to-make-a-mobile-html5-player.jpg) 3 | 4 | 项目完成于2016年,那时候刚刚了解前端,jQuery似乎是成为了必会的工具,为了提升实战能力于是做了此案例,由于项目包含的MP3文件较大,github仓库移除了res文件夹,完整项目文件见[码云仓库地址](https://gitee.com/alex1504/jquery-mobile-player),希望该教程对新人有所启发。 5 | 6 | 预览地址:请猛击[这里](http://alex1504.gitee.io/jquery-mobile-player) 7 | 8 | 注意:使用PC浏览最好打开**移动设备模式**,使用移动设备浏览需要关闭无痕浏览模式(否则无法使用本地存储,一般浏览器都是默认不开启),项目需要在**本地服务器**或**线上服务器**运行,以file:///形式的地址打开是无法进行ajax请求的,从而无法看到音乐数据。 9 | 10 | # 项目实现的功能及所用知识 # 11 | - 播放器的基础操作,上一首,下一首(顺序播放、随机播放、单曲循环),播放暂停,滑动时间轴的歌词定位 12 | - 初始handlebar模板渲染音乐列表数据,下拉滚动加载音乐列表数据。 13 | - 歌曲列表可添加喜爱音乐,于下次刷新时更新喜爱音乐列表,基于HTML5本地存储。 14 | - 布局采用rem布局,自适应移动端手机设备。 15 | - iconfont在线图标应用的使用 16 | 17 | # 项目目录文件结构 # 18 | - css:存放样式文件 19 | - lib: 存放公共脚本库 20 | - js: 存放项目脚本文件 21 | - img: 存放图片 22 | - fonts: 项目字体文件 23 | - res: 项目音乐资源 24 | - ui:项目ui文件(psd) 25 | 26 | # 项目js文件结构 # 27 | ```javascript 28 | // ============================配置变量================================ 29 | var rootPath = window.location.href.replace(/\/\w+\.\w+/, "/"); 30 | var Settings = { 31 | playmode: 0, //0列表循环,1随机,2为单曲循环 32 | volume: 0.5, //音量 33 | initNum: 10, //列表初始化歌曲数 34 | reqNum: 10 //后续请求歌曲数 35 | }; 36 | 37 | // ============================工具函数================================ 38 | var Util = (function() { 39 | return { 40 | 41 | } 42 | })() 43 | // ============================Dom选择器================================ 44 | var Dom = { 45 | 46 | } 47 | 48 | // ============================全局变量================================ 49 | var winH = $(window).height(); 50 | 51 | var songNum = 0; //当前列表歌曲数目 52 | var lrcHighIndex = 0; // 歌词高亮索引 53 | var lrcMoveIndex = 0; // 歌词移动单位索引 54 | var moveDis = 0; // 单句歌词每次移动距离 55 | 56 | var duration = 0; // 当前歌曲的时间 57 | var index = 0; //当前播放歌曲的索引 58 | var songInfo = null; // 当前歌曲信息 59 | var songModelUI = null; // 当前歌曲UI模型 60 | var timeArr = []; //当前歌曲时间数组 61 | var formatTimeArr = []; //当前歌曲时间数组(格式化为秒数) 62 | 63 | // ============================入口函数================================ 64 | function main() { 65 | initUIFrame(); 66 | var initModel = PlayerModel(); 67 | 68 | var songListUI = ModelUIFrame(Dom.songListContainer); 69 | var lsongListUI = ModelUIFrame(Dom.lSongListContainer); 70 | initModel.getSongList("data/data.json", function(data) { 71 | // 生成所有歌曲列表 72 | songListUI.renderList(data, 0, null, function() { 73 | songListUI.updateList(); 74 | }); 75 | // 生成喜爱歌曲列表 76 | initModel.getLoveSongArr(function(lSongArr) { 77 | lsongListUI.renderList(data, 1, lSongArr); 78 | }); 79 | // 添加动画 80 | Util.addAnimationDelay(Dom.song); 81 | // 保存歌词数据 82 | initModel.saveLyric(data); 83 | 84 | }); 85 | EventHandler(); 86 | } 87 | // ============================初始化UI函数================================ 88 | function initUIFrame() { 89 | 90 | } 91 | // ============================实现数据交互方法================================ 92 | function PlayerModel() { 93 | 94 | } 95 | // ============================模型动态UI模块================================ 96 | function ModelUIFrame(container) { 97 | 98 | } 99 | // ============================事件绑定模块================================ 100 | function EventHandler() { 101 | 102 | } 103 | // 调用入口函数 104 | main(); 105 | ``` 106 | 107 | # 功能点详解 # 108 | ## Handlebar.js初次渲染及滚动加载 ## 109 | 使用前端模板优点是把数据和结构分离出来,代码更清晰。但后来发现handlerbar.js似乎无法在js中示例模板对象,而html中的handlebar在初次进入页面便会被编译了,因此后续添加音乐还是采用传统的拼接字符串的方式,如果你有更优雅的动态加载方式,欢迎讨论交流。 110 | 111 | **html:** handlebars模板包含在script标签之中并且type类型为"text/x-handlebars-template",在初始化页面的时候根据js获取数据植入后就渲染出相应的html。 112 | ```html 113 | 131 | 132 | ``` 133 | **js:** 134 | 135 | ```javascript 136 | function renderAllList(data) { 137 | var preTpl; 138 | var lsongArr = Util.getItem('lsonglist') === null ? [] : JSON.parse(Util.getItem('lsonglist')); 139 | // 生成列表 140 | if (!sListTpl) { 141 | // 后续动态生成歌曲 142 | var tpl = ""; 143 | var songIndex = songNum; 144 | $.each(data, function(index, el) { 145 | if (index >= songIndex && index < songIndex + Settings.reqNum) { 146 | tpl += "
  • " + el.songName + "

    " + el.singer + "
  • "; 147 | songNum++; 148 | } 149 | }); 150 | $(container).append($(tpl)); 151 | } else { 152 | // 首次生成歌曲 153 | preTpl = Handlebars.compile(sListTpl); 154 | $(container).html(preTpl(data)); 155 | } 156 | // 更新喜爱图标 157 | if (lsongArr.length !== 0) { 158 | $.each(lsongArr, function(index, val) { 159 | Dom.songListContainer.find(".song").eq(val).find(".icon-love").addClass('active'); 160 | }); 161 | } 162 | } 163 | ``` 164 | 165 | ## rem布局自适应方案 ## 166 | 大体上指的是html根元素上定义一个字体大小,然后css样式定义时使用rem作为单位,包括margin、paddding、用于绝对定位的单位等等。然后js根据手机设备的屏幕大小,改变根字体的大小,这样整个页面也会跟着相应的缩小或放大。 167 | 168 | ```javascript 169 | // 根据手机屏幕自适应字体大小(rem自适应方案) 170 | function rescale() { 171 | var docEle = document.documentElement; 172 | var width = docEle.clientWidth || window.innerWidth; 173 | if (width > 640 && width !== 768) { 174 | docEle.style.fontSize = "100px"; 175 | } else if (width == 320) { 176 | //iphone5 177 | docEle.style.fontSize = "49px"; 178 | } else if (width == 375) { 179 | //iphone6 180 | docEle.style.fontSize = "57px"; 181 | } else if (width == 412) { 182 | // Nexus 5X 183 | docEle.style.fontSize = "57px"; 184 | } else if (width == 768) { 185 | //ipad 186 | docEle.style.fontSize = "88px"; 187 | } else { 188 | docEle.style.fontSize = Math.round(width / 640 * 100) + "px"; 189 | } 190 | } 191 | ``` 192 | 更多详解,请看先前一篇文章《移动端自适应布局解决方案——rem》,您可以猛击[这里](http://huzerui.com/blog/2016/06/16/mobile-rem/)跳转。 193 | 194 | ## 关于歌词的同步方案实现 ## 195 | 目前音乐播放器的歌词同步显示大概有两种,一种是精确到单个文字,一种是精确到单行歌词。本文实现的是第二种。 196 | ## 整体实现思路 ## 197 | 页面初始化时,请求歌曲数据json(本地json文件模拟),其中歌名、歌手、图片等按需渲染到html中,将歌词存储到localStorage中。此时,F12打开chrome调试器,进入Application-LocalStorage可以看到: 198 | ![歌词本地存储](http://huzerui.com/blog/img/post/2016-10-30-lyric-localstorage.jpg) 199 | 200 | 点击一首歌进入播放页面后,歌词就会从本地存储中读取,此时你会看到生成这样的歌词结构: 201 | ![歌词结构](http://huzerui.com/blog/img/post/2016-10-30-lyric-html.jpg) 202 | 每一行歌词都将要将歌词时间绑定在data-point上,监听歌曲播放的timeupdate事件,当歌曲的时间(经过取整处理)与当前data-point值相等时,就为当前歌词高亮(相当于给p添加current类名),并且根据当前高亮歌词的index索引将整个歌词盒子向上移动**p标签的高度+margin-top的高度**。 203 | 204 | ### lrc歌词的结构 ### 205 | 来自网易云音乐的歌词数据: 206 | ```javascript 207 | [00:14.64]如果不是那镜子\n[00:16.73]不像你不藏秘密\n[00:21.26]我还不肯相信\n[00:23.02]没有你我的笑更美丽\n[00:28.99]那天听你在电话里略带抱歉的关心\n 208 | 209 | [00:16.959]摘一颗苹果\n[00:19.800]等你从门前经过\n[00:22.700]送到你的手中帮你解渴\n[00:25.570]像夏天的可乐\n 210 | 211 | [00:00.00] 作曲 : 周杰伦\n[00:01.00] 作词 : 周杰伦\n[00:05.620]\n[00:37.980]亲吻你的手\n 212 | ``` 213 | 可以看到**格式 = [时间点] + 要显示的文字 + \n** 214 | 这里有两个坑需要注意: 215 | - 有的歌词秒数是精确到**小数点后两位**,**有的是三位**。 216 | - 有的歌词(周杰伦《算什么男人》)格式是 **[时间点]+\n** 217 | 218 | ### 时间歌词创建映射 ### 219 | 首先以\n将歌词字符串分割成以**[时间点]文字**的数组,但由于这样分割之后最后一个元素是空的,所以用**tempArr.splice(-1, 1)**删除最后一个元素。 220 | 221 | 接下来循环遍历这个临时数组,由于上面提到的秒数精确度的问题,所以判断一下index为9是否为数字,若为数字则将该位数字删除。(采用字符串截取方式,若你对js字符串方法不熟悉,可以猛击[这里](http://huzerui.com/blog/2016/06/14/js-string-method/)) 222 | 223 | 经过这样的处理之后,临时数组的元素格式不再有区别了,此时再进行字符串截取,将截取到的时间点放入timeArr,将截取的歌词放入lyricArr,并以返回保存着这两个变量的对象。 224 | ```javascript 225 | function createArrMap(lyric) { 226 | var timeArr = [], 227 | lyricArr = []; 228 | var tempArr = lyric.split("\n"); 229 | tempArr.splice(-1, 1); 230 | var tempStr = ""; 231 | $(tempArr).each(function(index) { 232 | tempStr = this; 233 | if (tempStr.charAt(9).match(/\d/) !== null) { 234 | tempStr = tempStr.substring(0, 9) + tempStr.substring(10); 235 | } 236 | timeArr.push(tempStr.substring(0, 10)); 237 | lyricArr.push(tempStr.substring(10)); 238 | }); 239 | return { 240 | timeArr: timeArr, 241 | lyricArr: lyricArr 242 | }; 243 | } 244 | ``` 245 | 246 | ### 生成歌词 ### 247 | 由于上面歌词格式造成时间点对应的歌词为空,此时如果渲染出一个

    标签的高度将为0,这会影响歌词向上移动距离的不统一。因此下面作出个判断如果为空,则替换为“--------------”。(为空的时候大多数是歌曲中间停顿或过渡的时候) 248 | ```javascript 249 | function renderLyric(songinfo) { 250 | var arrMap = Util.createArrMap(songinfo.lyric); 251 | var tpl = ""; 252 | $.each(arrMap.lyricArr, function(index, lyric) { 253 | var lyricContent = lyric === "" ? "--------------" : lyric; 254 | tpl += "

    " + lyricContent + "

    "; 255 | }); 256 | Dom.lrcwrap.html(tpl); 257 | } 258 | ``` 259 | 260 | ### 歌词同步 ### 261 | 歌词同步我写在了syncLyric方法中,监听audio元素的timeupdate事件调用。 262 | 这个方法接收两个参数,第一个是当前播放歌曲时间(秒),第二个是转化为秒数的时间点数组。 263 | 如果当前时间>=时间点,那么高亮当前歌词(以lrcHighIndex)存储,并且lrcHighIndex自增1。 264 | 当歌词高亮索引lrcHighIndex>=1即歌词高亮不为第一句时,计算索引并让歌词盒子向上移动。 265 | ```javascript 266 | function syncLyric(curS, formatTimeArr) { 267 | if (Math.floor(curS) >= formatTimeArr[lrcHighIndex]) { 268 | Dom.lrc.eq(lrcHighIndex).addClass('current').siblings().removeClass('current'); 269 | if (lrcHighIndex >= 1) { 270 | lrcMoveIndex = lrcHighIndex - 2; 271 | moveDis += Util.getMoveDis(lrcMoveIndex); 272 | Dom.lrcwrap.animate({ 273 | "top": "-" + moveDis + "px" 274 | }, 100); 275 | lrcMoveIndex++; 276 | } 277 | lrcHighIndex++; 278 | } 279 | } 280 | ``` 281 | 282 | # 后记(于2017-11-9) 283 | 时隔两年,回顾当年所走的路,不仅感概前端的变化远快于自己的成长速度,不过庆幸的是自己并没有选错这条路,因为我发现开发不仅已经成为了自己的工作,更是融入了自己的生活,成为生活的一部分;其次,这是开发者最好的时代,我不仅能从互联网挖掘我所有想要深入的知识,也感受者开源社区带给我的魅力,希望自己能从知识的接收者变成知识的传播者,与您共勉。 284 | -------------------------------------------------------------------------------- /css/index.css: -------------------------------------------------------------------------------- 1 | /* 加载页 */ 2 | .m-loader { 3 | position: fixed; 4 | z-index: 9999; 5 | width: 100%; 6 | height: 100%; 7 | top: 0; 8 | } 9 | .loader_overlay { 10 | width: 150px; 11 | height: 150px; 12 | background: transparent; 13 | box-shadow: 0px 0px 0px 1000px rgba(255, 255, 255, 0.67), 0px 0px 19px 0px rgba(0, 0, 0, 0.16) inset; 14 | border-radius: 100%; 15 | z-index: -1; 16 | position: absolute; 17 | left: 0; 18 | right: 0; 19 | top: 0; 20 | bottom: 0; 21 | margin: auto; 22 | } 23 | .loader_cogs { 24 | z-index: -2; 25 | width: 100px; 26 | height: 100px; 27 | top: -120px !important; 28 | position: absolute; 29 | left: 0; 30 | right: 0; 31 | top: 0; 32 | bottom: 0; 33 | margin: auto; 34 | } 35 | .loader_cogs__top { 36 | position: relative; 37 | width: 100px; 38 | height: 100px; 39 | -webkit-transform-origin: 50px 50px; 40 | transform-origin: 50px 50px; 41 | -webkit-animation: rotate 10s infinite linear; 42 | animation: rotate 10s infinite linear; 43 | } 44 | .loader_cogs__top div:nth-of-type(1) { 45 | -webkit-transform: rotate(30deg); 46 | transform: rotate(30deg); 47 | } 48 | .loader_cogs__top div:nth-of-type(2) { 49 | -webkit-transform: rotate(60deg); 50 | transform: rotate(60deg); 51 | } 52 | .loader_cogs__top div:nth-of-type(3) { 53 | -webkit-transform: rotate(90deg); 54 | transform: rotate(90deg); 55 | } 56 | .loader_cogs__top div.top_part { 57 | width: 100px; 58 | border-radius: 10px; 59 | position: absolute; 60 | height: 100px; 61 | background: #f98db9; 62 | } 63 | .loader_cogs__top div.top_hole { 64 | width: 50px; 65 | height: 50px; 66 | border-radius: 100%; 67 | background: white; 68 | position: absolute; 69 | position: absolute; 70 | left: 0; 71 | right: 0; 72 | top: 0; 73 | bottom: 0; 74 | margin: auto; 75 | } 76 | .loader_cogs__left { 77 | position: relative; 78 | width: 80px; 79 | -webkit-transform: rotate(16deg); 80 | transform: rotate(16deg); 81 | top: 28px; 82 | -webkit-transform-origin: 40px 40px; 83 | transform-origin: 40px 40px; 84 | -webkit-animation: rotate_left 10s .1s infinite reverse linear; 85 | animation: rotate_left 10s .1s infinite reverse linear; 86 | left: -24px; 87 | height: 80px; 88 | } 89 | .loader_cogs__left div:nth-of-type(1) { 90 | -webkit-transform: rotate(30deg); 91 | transform: rotate(30deg); 92 | } 93 | .loader_cogs__left div:nth-of-type(2) { 94 | -webkit-transform: rotate(60deg); 95 | transform: rotate(60deg); 96 | } 97 | .loader_cogs__left div:nth-of-type(3) { 98 | -webkit-transform: rotate(90deg); 99 | transform: rotate(90deg); 100 | } 101 | .loader_cogs__left div.left_part { 102 | width: 80px; 103 | border-radius: 6px; 104 | position: absolute; 105 | height: 80px; 106 | background: #97ddff; 107 | } 108 | .loader_cogs__left div.left_hole { 109 | width: 40px; 110 | height: 40px; 111 | border-radius: 100%; 112 | background: white; 113 | position: absolute; 114 | position: absolute; 115 | left: 0; 116 | right: 0; 117 | top: 0; 118 | bottom: 0; 119 | margin: auto; 120 | } 121 | .loader_cogs__bottom { 122 | position: relative; 123 | width: 60px; 124 | top: -65px; 125 | -webkit-transform-origin: 30px 30px; 126 | transform-origin: 30px 30px; 127 | -webkit-animation: rotate_left 10.2s .4s infinite linear; 128 | animation: rotate_left 10.2s .4s infinite linear; 129 | -webkit-transform: rotate(4deg); 130 | transform: rotate(4deg); 131 | left: 79px; 132 | height: 60px; 133 | } 134 | .loader_cogs__bottom div:nth-of-type(1) { 135 | -webkit-transform: rotate(30deg); 136 | transform: rotate(30deg); 137 | } 138 | .loader_cogs__bottom div:nth-of-type(2) { 139 | -webkit-transform: rotate(60deg); 140 | transform: rotate(60deg); 141 | } 142 | .loader_cogs__bottom div:nth-of-type(3) { 143 | -webkit-transform: rotate(90deg); 144 | transform: rotate(90deg); 145 | } 146 | .loader_cogs__bottom div.bottom_part { 147 | width: 60px; 148 | border-radius: 5px; 149 | position: absolute; 150 | height: 60px; 151 | background: #ffcd66; 152 | } 153 | .loader_cogs__bottom div.bottom_hole { 154 | width: 30px; 155 | height: 30px; 156 | border-radius: 100%; 157 | background: white; 158 | position: absolute; 159 | position: absolute; 160 | left: 0; 161 | right: 0; 162 | top: 0; 163 | bottom: 0; 164 | margin: auto; 165 | } 166 | 167 | /* Animations */ 168 | @-webkit-keyframes rotate { 169 | from { 170 | -webkit-transform: rotate(0deg); 171 | transform: rotate(0deg); 172 | } 173 | to { 174 | -webkit-transform: rotate(360deg); 175 | transform: rotate(360deg); 176 | } 177 | } 178 | @keyframes rotate { 179 | from { 180 | -webkit-transform: rotate(0deg); 181 | transform: rotate(0deg); 182 | } 183 | to { 184 | -webkit-transform: rotate(360deg); 185 | transform: rotate(360deg); 186 | } 187 | } 188 | @-webkit-keyframes rotate_left { 189 | from { 190 | -webkit-transform: rotate(16deg); 191 | transform: rotate(16deg); 192 | } 193 | to { 194 | -webkit-transform: rotate(376deg); 195 | transform: rotate(376deg); 196 | } 197 | } 198 | @keyframes rotate_left { 199 | from { 200 | -webkit-transform: rotate(16deg); 201 | transform: rotate(16deg); 202 | } 203 | to { 204 | -webkit-transform: rotate(376deg); 205 | transform: rotate(376deg); 206 | } 207 | } 208 | @-webkit-keyframes rotate_right { 209 | from { 210 | -webkit-transform: rotate(4deg); 211 | transform: rotate(4deg); 212 | } 213 | to { 214 | -webkit-transform: rotate(364deg); 215 | transform: rotate(364deg); 216 | } 217 | } 218 | @keyframes rotate_right { 219 | from { 220 | -webkit-transform: rotate(4deg); 221 | transform: rotate(4deg); 222 | } 223 | to { 224 | -webkit-transform: rotate(364deg); 225 | transform: rotate(364deg); 226 | } 227 | } 228 | 229 | 230 | /* 主页 */ 231 | .index{ 232 | 233 | } 234 | .index .g-header{ 235 | position: fixed; 236 | width: 100%; 237 | z-index: 9999; 238 | line-height: 0.86rem; 239 | text-align: center; 240 | justify-contents: center; 241 | box-sizing: border-box; 242 | } 243 | .index .m-header{ 244 | height: 100%; 245 | display: flex; 246 | 247 | } 248 | .index .m-header .cog, .index .m-header .user{ 249 | width: 0.96rem; 250 | height: 100%; 251 | font-size: 0.5rem; 252 | } 253 | .index .m-header .cog{ 254 | 255 | } 256 | .index .m-header .title{ 257 | margin: 0 auto; 258 | width: 4.68rem; 259 | height: 100%; 260 | } 261 | 262 | .index .g-search{ 263 | height: 0.94rem; 264 | overflow: hidden; 265 | font-size: 0.24rem; 266 | } 267 | .index .m-search{ 268 | width: 98%; 269 | height: 0.56rem; 270 | line-height: 0.56rem; 271 | margin: 0.16rem auto; 272 | position: relative; 273 | 274 | 275 | } 276 | .index .m-search input{ 277 | padding-left: 0.28rem; 278 | box-sizing: border-box; 279 | width: 100%; 280 | height: 100%; 281 | background-color: rgba(12, 18, 19, 0.39); 282 | border:none; 283 | color:#fff; 284 | } 285 | .index .m-search .icon-search{ 286 | position:absolute; 287 | right: 0.15rem; 288 | top: 0.14rem; 289 | font-size: 0.3rem; 290 | 291 | } 292 | 293 | 294 | .index .g-nav{ 295 | font-size: 0.18rem; 296 | height: 0.94rem; 297 | overflow: hidden; 298 | box-sizing: border-box; 299 | box-shadow: 0 2px 4px #08293f; 300 | 301 | } 302 | .index .m-nav{ 303 | display: flex; 304 | height: 0.6rem; 305 | line-height: 0.6rem; 306 | margin: 0.18rem auto; 307 | text-align: center; 308 | } 309 | .index .m-nav .nav{ 310 | flex-grow: 1; 311 | margin: 0 0.14rem; 312 | text-align: center; 313 | 314 | border: 1px solid #fff; 315 | font-size: 0.3rem; 316 | 317 | } 318 | .index .m-nav .nav.active{ 319 | background-color: #4ba292; 320 | } 321 | .index .m-nav .list{ 322 | 323 | } 324 | .index .m-nav .love{ 325 | 326 | } 327 | .index .m-nav .search{ 328 | 329 | } 330 | 331 | 332 | .index .g-songlist{ 333 | position: relative; 334 | z-index: 9; 335 | top: 2.82rem; 336 | width: 300vw; 337 | 338 | font-size: 0.24rem; 339 | 340 | overflow: hidden; // 外围包裹,设置为overflow:hidden,限制区域以防溢出 341 | -webkit-transition: all .3s ease-out; 342 | transition: all .3s ease-out; 343 | 344 | 345 | } 346 | .index .sliderWrap{ 347 | width: 100vw; 348 | overflow-y: auto; // 滑动容器,定义该属性的容器用于滑动 349 | height: auto; 350 | float: left; 351 | height: calc(100vh - 2.76rem); 352 | -webkit-overflow-scrolling: touch; // 让局部滚动成为全局滚动 353 | } 354 | 355 | .index .m-songlist{ 356 | 357 | width: 100vw; 358 | 359 | height: auto; 360 | float: left; 361 | } 362 | .index .m-songlist-l{ 363 | text-align: center; 364 | } 365 | .index .m-songlist-s{ 366 | text-align: center; 367 | } 368 | 369 | @keyframes fadeIn{ 370 | from{ 371 | opacity:0 372 | }to{ 373 | opacity:1 374 | } 375 | } 376 | .index .m-songlist .song{ 377 | height: 1.80rem; 378 | animation-name: fadeIn; 379 | -webkit-animation-name: fadeIn; 380 | animation-duration: .6s; 381 | -webkit-animation-duration: .6s; 382 | animation-fill-mode: both; 383 | -webkit-animation-fill-mode: both; 384 | } 385 | .index .m-songlist .song.active{ 386 | color: #bfd835; 387 | } 388 | .index .m-songlist .poster, .index .m-songlist .songinfo{ 389 | height: 100%; 390 | box-sizing: border-box; 391 | float: left; 392 | } 393 | .index .m-songlist .poster{ 394 | display: flex; 395 | align: center; 396 | width: 1.74rem; 397 | line-height: 1.80rem; 398 | padding:0.16rem; 399 | text-align: center; 400 | } 401 | .index .m-songlist .songinfo{ 402 | padding-top: 0.5rem; 403 | width: 3.78rem; 404 | padding-left: 0.4rem; 405 | text-align: left; 406 | } 407 | .index .m-songlist .loveflag{ 408 | width: 0.88rem; 409 | float: right; 410 | box-sizing: border-box; 411 | width: 1rem; 412 | justify-content: center; 413 | display: flex; 414 | align-items: center; 415 | height: 100%; 416 | position: relative; 417 | } 418 | .index .m-songlist .icon-love{ 419 | display:block; 420 | width: 0.52rem; 421 | height:0.52rem; 422 | background:url(../img/icon_love.png) no-repeat center center; 423 | background-size: cover; 424 | } 425 | .index .m-songlist .icon-love.active{ 426 | background:url(../img/icon_love_active.png) no-repeat center center; 427 | background-size: cover; 428 | } 429 | 430 | 431 | .index .g-footer{ 432 | position: fixed; 433 | z-index: 99; 434 | background: url(../img/bg_bottom.jpg) no-repeat center center; 435 | background-size: cover; 436 | width: 100%; 437 | bottom: 0; 438 | height: 0.92rem; 439 | line-height: 0.92rem; 440 | text-align: center; 441 | } 442 | .index .m-footer{ 443 | width:4.64rem; 444 | height: 100%; 445 | margin: 0 auto; 446 | } 447 | .index .m-footer .tip{ 448 | height: 100%; 449 | } 450 | .index .m-footer .tip::before{ 451 | content:""; 452 | display:inline-block; 453 | width: 0.36rem; 454 | height: 0.36rem; 455 | background:url(../img/cd.png) no-repeat center center; 456 | background-size: cover; 457 | vertical-align: -4%; 458 | } 459 | /* END主页 */ 460 | 461 | /* 播放页 */ 462 | .play{ 463 | position: fixed; 464 | width: 100%; 465 | height: 100%; 466 | 467 | top: 0; 468 | left: 0; 469 | z-index: 999999; 470 | -webkit-transform: translateY(-100%); 471 | transform: translateY(-100%); 472 | -webkit-transition: all .3s ease-in-out; 473 | transition: all .3s ease-in-out; 474 | -webkit-overflow-scrolling: touch; 475 | overflow-y: scroll; 476 | 477 | } 478 | /* 头部 */ 479 | .play .g-header{ 480 | height: 0.86rem; 481 | line-height: 0.86rem; 482 | text-align: center; 483 | justify-contents: center; 484 | box-sizing: border-box; 485 | } 486 | .play .m-header{ 487 | height: 100%; 488 | display: flex; 489 | 490 | } 491 | .play .m-header .back, .play .m-header .user{ 492 | width: 0.86rem; 493 | height: 100%; 494 | margin: 0 auto; 495 | font-size: 0.5rem; 496 | } 497 | .play .m-header .cog{ 498 | 499 | } 500 | .play .m-header .title{ 501 | width: 4.68rem; 502 | height: 100%; 503 | } 504 | 505 | /* 主体 */ 506 | .play .g-main{ 507 | height: calc(100vh - 0.86rem); 508 | } 509 | 510 | /* 音量控制模块 */ 511 | .play .g-volctrl{ 512 | height: 1.14rem; 513 | line-height: 1.14rem; 514 | } 515 | .play .m-volctrl{ 516 | width: 5.60rem; 517 | height: 100%; 518 | margin: 0 auto; 519 | } 520 | .play .m-volctrl .icon, .play .m-volctrl .m-progress{ 521 | float: left; 522 | } 523 | .play .m-volctrl .icon{ 524 | margin-top: 0.38rem; 525 | } 526 | .play .m-volctrl .icon-volume-high{ 527 | float: right; 528 | } 529 | .play .m-progress{ 530 | width: 4.64rem; 531 | height: 0.08rem; 532 | background-color: #162227; 533 | margin-top: 0.47rem; 534 | margin-left: 0.02rem; 535 | position: relative; 536 | } 537 | .play .m-progress .progress, .play .m-progress .progressbar{ 538 | position: absolute; 539 | } 540 | .play .m-progress .progress{ 541 | top: 0; 542 | height: 100%; 543 | width: 0%; 544 | background-color: #62c6c7; 545 | } 546 | .play .m-progress .progressbar{ 547 | top: 50%; 548 | width: 0.30rem; 549 | height: 0.30rem; 550 | left: 0%; 551 | background-color: #fff; 552 | border-radius: 50%; 553 | margin-top: -0.15rem; 554 | } 555 | .play .m-volctrl .progressbar{ 556 | left: 10%; 557 | } 558 | .play .m-volctrl .progress{ 559 | width: 10%; 560 | } 561 | /* 歌词模块 */ 562 | .play .g-lrc{ 563 | height: 4.60rem; 564 | } 565 | .play .m-lrc{ 566 | width: 4.64rem; 567 | height: 100%; 568 | margin: 0 auto; 569 | background-color: rgba(255, 255, 255, 0.2); 570 | box-shadow: 1px 1px 4px #333; 571 | overflow: hidden; 572 | } 573 | .play .m-lrc .lrc-box{ 574 | position:relative; 575 | width:3.88rem; 576 | height: 3.92rem; 577 | margin: 0.36rem auto 0; 578 | background-color: rgba(119, 140, 155, 0.4); 579 | border: 1px solid #8a9daa; 580 | box-shadow: 0 0 10px 2px rgba(97, 95, 95, 0.71); 581 | } 582 | .play .m-lrc .lrc-no, .play .m-lrc .lrc{ 583 | position: absolute; 584 | top: 0; 585 | width: 100%; 586 | height: 100%; 587 | } 588 | .play .m-lrc .lrc-no{ 589 | display:flex; 590 | justify-content:center; 591 | align-items:center; 592 | } 593 | @-webkit-keyframes rotate{ 594 | from{ 595 | transform: rotate(0deg) 596 | }to{ 597 | transform: rotate(360deg) 598 | } 599 | } 600 | @keyframes rotate{ 601 | from{ 602 | transform: rotate(0deg) 603 | }to{ 604 | transform: rotate(360deg) 605 | } 606 | } 607 | .play .m-lrc .lrc-no .lrc-bg{ 608 | -webkit-animation: rotate 40s linear infinite; 609 | animation: rotate 40s linear infinite; 610 | background-repeat: no-repeat; 611 | background-position: center center; 612 | background-size: cover; 613 | width: 3rem; 614 | height: 3rem; 615 | } 616 | .play .m-lrc .lrc{ 617 | overflow:hidden; 618 | top: 0; 619 | } 620 | .play .m-lrc .lrc-wrap{ 621 | position: absolute; 622 | width: 100%; 623 | height: 100%; 624 | top: 0; 625 | } 626 | .play .m-lrc .lrc-wrap p{ 627 | text-align: center; 628 | margin: 0.20rem 0.2rem; 629 | font-size: 0.24rem; 630 | } 631 | .play .m-lrc .lrc-wrap p.current{ 632 | color: #bfd835; 633 | font-size: 0.26rem; 634 | } 635 | 636 | 637 | /* 歌曲信息模块 */ 638 | .play .g-songinfo{ 639 | height: 1.50rem; 640 | } 641 | .play .m-songinfo{ 642 | width: 4.64rem; 643 | height: 100%; 644 | margin: 0 auto; 645 | overflow: hidden; 646 | } 647 | .play .m-songinfo .songname,.play .m-songinfo .singer{ 648 | text-align: center; 649 | font-weight: normal; 650 | margin-bottom: 0; 651 | } 652 | .play .m-songinfo .songname{ 653 | font-size: 0.34rem; 654 | margin-top: 0.44rem; 655 | 656 | } 657 | .play .m-songinfo .singer{ 658 | font-size: 0.20rem; 659 | } 660 | 661 | 662 | /* 音乐控制模块 */ 663 | .play .g-panel{ 664 | height: 3.34rem; 665 | } 666 | .play .g-panel .m-progress{ 667 | margin: 0 auto; 668 | height: 0.08rem; 669 | } 670 | .play .g-panel .m-progress .songtime{ 671 | font-size: 0.18rem; 672 | position:relative; 673 | top: 0.20rem; 674 | } 675 | .play .g-panel .m-progress .start{ 676 | float:left; 677 | } 678 | .play .g-panel .m-progress .end{ 679 | float:right; 680 | } 681 | 682 | .play .m-playcontrol{ 683 | width: 3.9rem; 684 | margin: 0.34rem auto 0; 685 | text-align: center; 686 | } 687 | .play .m-playcontrol .btn{ 688 | display: inline-block; 689 | background-size: cover; 690 | 691 | } 692 | .play .m-playcontrol .btn-prev{ 693 | width: 0.87rem; 694 | height:0.75rem; 695 | background: url(../img/pre.png) no-repeat center center; 696 | background-size: cover; 697 | } 698 | 699 | .play .m-playcontrol .btn-next{ 700 | width: 0.87rem; 701 | height:0.75rem; 702 | background: url(../img/next.png) no-repeat center center; 703 | background-size: cover; 704 | } 705 | .play .m-playcontrol .btn-play, .play .m-playcontrol .btn-pause{ 706 | width: 1.37rem; 707 | height: 1.27rem; 708 | } 709 | .play .m-playcontrol .btn-play{ 710 | background: url(../img/play.png) no-repeat center center; 711 | background-size: cover; 712 | } 713 | .play .m-playcontrol .btn-pause{ 714 | background: url(../img/pause.png) no-repeat center center; 715 | background-size: cover; 716 | } 717 | .play .m-playoptions{ 718 | height: 1.14rem; 719 | display:flex; 720 | width: 100%; 721 | bottom: 0; 722 | } 723 | .play .m-playoptions a{ 724 | display:flex; 725 | justify-content: center; 726 | align-items:center; 727 | flex: 1; 728 | color: #0e202c; 729 | } 730 | .play .m-playoptions a.active{ 731 | color:#fff; 732 | } 733 | 734 | /* 歌曲列表模块 */ 735 | .play .g-list{ 736 | position: fixed; 737 | width: 100%; 738 | height:8.62rem; 739 | bottom:1.14rem; 740 | background:url(../img/bg_list.jpg) no-repeat center center; 741 | background-size: cover; 742 | } 743 | .play .m-list{ 744 | margin-bottom: 1.0rem; 745 | width: 4.64rem; 746 | margin: 0 auto; 747 | height: 7.58rem; 748 | overflow-y: scroll; 749 | -webkit-overflow-scrolling: touch; 750 | } 751 | 752 | .play .m-list::-webkit-scrollbar{ 753 | opacity: 0; 754 | } 755 | 756 | .play .m-list .list{ 757 | 758 | } 759 | .play .m-list .list .song{ 760 | position:relative; 761 | padding: 0.24rem 0; 762 | border-bottom: 1px solid #fff; 763 | } 764 | .play .m-list .song .songinfo{ 765 | 766 | } 767 | .play .m-list .song .songinfo h1{ 768 | font-size: 0.40rem; 769 | margin: 0; 770 | font-weight: normal; 771 | } 772 | .play .m-list .song .songinfo h2{ 773 | font-size: 0.30rem; 774 | font-weight: normal; 775 | } 776 | .play .m-list .song .icon-cross{ 777 | position:absolute; 778 | top: 50%; 779 | margin-top: -0.14rem; 780 | right: 0; 781 | } 782 | 783 | /* END主体 */ 784 | /* END播放页 */ 785 | 786 | /* 用户页 */ 787 | .g-user{ 788 | position: fixed; 789 | z-index: 99999; 790 | top:0; 791 | display: flex; 792 | align-items: center; 793 | width: 100%; 794 | height: 100%; 795 | background: url(../img/bg.jpg) no-repeat center center; 796 | background-size: cover; 797 | transform: translateX(100%); 798 | transition: all .6s; 799 | } 800 | .m-user{ 801 | color: #fefefe; 802 | text-align: justify; 803 | } 804 | .m-user h2{ 805 | text-align: center; 806 | margin-top: 0.2rem; 807 | font-size: 0.6rem; 808 | } 809 | .m-user p{ 810 | padding: 0 0.8rem; 811 | font-size: 0.4rem; 812 | } 813 | /* END用户页 */ -------------------------------------------------------------------------------- /lib/jquery.touchSwipe.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * @fileOverview TouchSwipe - jQuery Plugin 3 | * @version 1.6.18 4 | * 5 | * @author Matt Bryson http://www.github.com/mattbryson 6 | * @see https://github.com/mattbryson/TouchSwipe-Jquery-Plugin 7 | * @see http://labs.rampinteractive.co.uk/touchSwipe/ 8 | * @see http://plugins.jquery.com/project/touchSwipe 9 | * @license 10 | * Copyright (c) 2010-2015 Matt Bryson 11 | * Dual licensed under the MIT or GPL Version 2 licenses. 12 | * 13 | */ 14 | !function(factory){"function"==typeof define&&define.amd&&define.amd.jQuery?define(["jquery"],factory):factory("undefined"!=typeof module&&module.exports?require("jquery"):jQuery)}(function($){"use strict";function init(options){return!options||void 0!==options.allowPageScroll||void 0===options.swipe&&void 0===options.swipeStatus||(options.allowPageScroll=NONE),void 0!==options.click&&void 0===options.tap&&(options.tap=options.click),options||(options={}),options=$.extend({},$.fn.swipe.defaults,options),this.each(function(){var $this=$(this),plugin=$this.data(PLUGIN_NS);plugin||(plugin=new TouchSwipe(this,options),$this.data(PLUGIN_NS,plugin))})}function TouchSwipe(element,options){function touchStart(jqEvent){if(!(getTouchInProgress()||$(jqEvent.target).closest(options.excludedElements,$element).length>0)){var event=jqEvent.originalEvent?jqEvent.originalEvent:jqEvent;if(!event.pointerType||"mouse"!=event.pointerType||0!=options.fallbackToMouseEvents){var ret,touches=event.touches,evt=touches?touches[0]:event;return phase=PHASE_START,touches?fingerCount=touches.length:options.preventDefaultEvents!==!1&&jqEvent.preventDefault(),distance=0,direction=null,currentDirection=null,pinchDirection=null,duration=0,startTouchesDistance=0,endTouchesDistance=0,pinchZoom=1,pinchDistance=0,maximumsMap=createMaximumsData(),cancelMultiFingerRelease(),createFingerData(0,evt),!touches||fingerCount===options.fingers||options.fingers===ALL_FINGERS||hasPinches()?(startTime=getTimeStamp(),2==fingerCount&&(createFingerData(1,touches[1]),startTouchesDistance=endTouchesDistance=calculateTouchesDistance(fingerData[0].start,fingerData[1].start)),(options.swipeStatus||options.pinchStatus)&&(ret=triggerHandler(event,phase))):ret=!1,ret===!1?(phase=PHASE_CANCEL,triggerHandler(event,phase),ret):(options.hold&&(holdTimeout=setTimeout($.proxy(function(){$element.trigger("hold",[event.target]),options.hold&&(ret=options.hold.call($element,event,event.target))},this),options.longTapThreshold)),setTouchInProgress(!0),null)}}}function touchMove(jqEvent){var event=jqEvent.originalEvent?jqEvent.originalEvent:jqEvent;if(phase!==PHASE_END&&phase!==PHASE_CANCEL&&!inMultiFingerRelease()){var ret,touches=event.touches,evt=touches?touches[0]:event,currentFinger=updateFingerData(evt);if(endTime=getTimeStamp(),touches&&(fingerCount=touches.length),options.hold&&clearTimeout(holdTimeout),phase=PHASE_MOVE,2==fingerCount&&(0==startTouchesDistance?(createFingerData(1,touches[1]),startTouchesDistance=endTouchesDistance=calculateTouchesDistance(fingerData[0].start,fingerData[1].start)):(updateFingerData(touches[1]),endTouchesDistance=calculateTouchesDistance(fingerData[0].end,fingerData[1].end),pinchDirection=calculatePinchDirection(fingerData[0].end,fingerData[1].end)),pinchZoom=calculatePinchZoom(startTouchesDistance,endTouchesDistance),pinchDistance=Math.abs(startTouchesDistance-endTouchesDistance)),fingerCount===options.fingers||options.fingers===ALL_FINGERS||!touches||hasPinches()){if(direction=calculateDirection(currentFinger.start,currentFinger.end),currentDirection=calculateDirection(currentFinger.last,currentFinger.end),validateDefaultEvent(jqEvent,currentDirection),distance=calculateDistance(currentFinger.start,currentFinger.end),duration=calculateDuration(),setMaxDistance(direction,distance),ret=triggerHandler(event,phase),!options.triggerOnTouchEnd||options.triggerOnTouchLeave){var inBounds=!0;if(options.triggerOnTouchLeave){var bounds=getbounds(this);inBounds=isInBounds(currentFinger.end,bounds)}!options.triggerOnTouchEnd&&inBounds?phase=getNextPhase(PHASE_MOVE):options.triggerOnTouchLeave&&!inBounds&&(phase=getNextPhase(PHASE_END)),phase!=PHASE_CANCEL&&phase!=PHASE_END||triggerHandler(event,phase)}}else phase=PHASE_CANCEL,triggerHandler(event,phase);ret===!1&&(phase=PHASE_CANCEL,triggerHandler(event,phase))}}function touchEnd(jqEvent){var event=jqEvent.originalEvent?jqEvent.originalEvent:jqEvent,touches=event.touches;if(touches){if(touches.length&&!inMultiFingerRelease())return startMultiFingerRelease(event),!0;if(touches.length&&inMultiFingerRelease())return!0}return inMultiFingerRelease()&&(fingerCount=fingerCountAtRelease),endTime=getTimeStamp(),duration=calculateDuration(),didSwipeBackToCancel()||!validateSwipeDistance()?(phase=PHASE_CANCEL,triggerHandler(event,phase)):options.triggerOnTouchEnd||options.triggerOnTouchEnd===!1&&phase===PHASE_MOVE?(options.preventDefaultEvents!==!1&&jqEvent.preventDefault(),phase=PHASE_END,triggerHandler(event,phase)):!options.triggerOnTouchEnd&&hasTap()?(phase=PHASE_END,triggerHandlerForGesture(event,phase,TAP)):phase===PHASE_MOVE&&(phase=PHASE_CANCEL,triggerHandler(event,phase)),setTouchInProgress(!1),null}function touchCancel(){fingerCount=0,endTime=0,startTime=0,startTouchesDistance=0,endTouchesDistance=0,pinchZoom=1,cancelMultiFingerRelease(),setTouchInProgress(!1)}function touchLeave(jqEvent){var event=jqEvent.originalEvent?jqEvent.originalEvent:jqEvent;options.triggerOnTouchLeave&&(phase=getNextPhase(PHASE_END),triggerHandler(event,phase))}function removeListeners(){$element.unbind(START_EV,touchStart),$element.unbind(CANCEL_EV,touchCancel),$element.unbind(MOVE_EV,touchMove),$element.unbind(END_EV,touchEnd),LEAVE_EV&&$element.unbind(LEAVE_EV,touchLeave),setTouchInProgress(!1)}function getNextPhase(currentPhase){var nextPhase=currentPhase,validTime=validateSwipeTime(),validDistance=validateSwipeDistance(),didCancel=didSwipeBackToCancel();return!validTime||didCancel?nextPhase=PHASE_CANCEL:!validDistance||currentPhase!=PHASE_MOVE||options.triggerOnTouchEnd&&!options.triggerOnTouchLeave?!validDistance&¤tPhase==PHASE_END&&options.triggerOnTouchLeave&&(nextPhase=PHASE_CANCEL):nextPhase=PHASE_END,nextPhase}function triggerHandler(event,phase){var ret,touches=event.touches;return(didSwipe()||hasSwipes())&&(ret=triggerHandlerForGesture(event,phase,SWIPE)),(didPinch()||hasPinches())&&ret!==!1&&(ret=triggerHandlerForGesture(event,phase,PINCH)),didDoubleTap()&&ret!==!1?ret=triggerHandlerForGesture(event,phase,DOUBLE_TAP):didLongTap()&&ret!==!1?ret=triggerHandlerForGesture(event,phase,LONG_TAP):didTap()&&ret!==!1&&(ret=triggerHandlerForGesture(event,phase,TAP)),phase===PHASE_CANCEL&&touchCancel(event),phase===PHASE_END&&(touches?touches.length||touchCancel(event):touchCancel(event)),ret}function triggerHandlerForGesture(event,phase,gesture){var ret;if(gesture==SWIPE){if($element.trigger("swipeStatus",[phase,direction||null,distance||0,duration||0,fingerCount,fingerData,currentDirection]),options.swipeStatus&&(ret=options.swipeStatus.call($element,event,phase,direction||null,distance||0,duration||0,fingerCount,fingerData,currentDirection),ret===!1))return!1;if(phase==PHASE_END&&validateSwipe()){if(clearTimeout(singleTapTimeout),clearTimeout(holdTimeout),$element.trigger("swipe",[direction,distance,duration,fingerCount,fingerData,currentDirection]),options.swipe&&(ret=options.swipe.call($element,event,direction,distance,duration,fingerCount,fingerData,currentDirection),ret===!1))return!1;switch(direction){case LEFT:$element.trigger("swipeLeft",[direction,distance,duration,fingerCount,fingerData,currentDirection]),options.swipeLeft&&(ret=options.swipeLeft.call($element,event,direction,distance,duration,fingerCount,fingerData,currentDirection));break;case RIGHT:$element.trigger("swipeRight",[direction,distance,duration,fingerCount,fingerData,currentDirection]),options.swipeRight&&(ret=options.swipeRight.call($element,event,direction,distance,duration,fingerCount,fingerData,currentDirection));break;case UP:$element.trigger("swipeUp",[direction,distance,duration,fingerCount,fingerData,currentDirection]),options.swipeUp&&(ret=options.swipeUp.call($element,event,direction,distance,duration,fingerCount,fingerData,currentDirection));break;case DOWN:$element.trigger("swipeDown",[direction,distance,duration,fingerCount,fingerData,currentDirection]),options.swipeDown&&(ret=options.swipeDown.call($element,event,direction,distance,duration,fingerCount,fingerData,currentDirection))}}}if(gesture==PINCH){if($element.trigger("pinchStatus",[phase,pinchDirection||null,pinchDistance||0,duration||0,fingerCount,pinchZoom,fingerData]),options.pinchStatus&&(ret=options.pinchStatus.call($element,event,phase,pinchDirection||null,pinchDistance||0,duration||0,fingerCount,pinchZoom,fingerData),ret===!1))return!1;if(phase==PHASE_END&&validatePinch())switch(pinchDirection){case IN:$element.trigger("pinchIn",[pinchDirection||null,pinchDistance||0,duration||0,fingerCount,pinchZoom,fingerData]),options.pinchIn&&(ret=options.pinchIn.call($element,event,pinchDirection||null,pinchDistance||0,duration||0,fingerCount,pinchZoom,fingerData));break;case OUT:$element.trigger("pinchOut",[pinchDirection||null,pinchDistance||0,duration||0,fingerCount,pinchZoom,fingerData]),options.pinchOut&&(ret=options.pinchOut.call($element,event,pinchDirection||null,pinchDistance||0,duration||0,fingerCount,pinchZoom,fingerData))}}return gesture==TAP?phase!==PHASE_CANCEL&&phase!==PHASE_END||(clearTimeout(singleTapTimeout),clearTimeout(holdTimeout),hasDoubleTap()&&!inDoubleTap()?(doubleTapStartTime=getTimeStamp(),singleTapTimeout=setTimeout($.proxy(function(){doubleTapStartTime=null,$element.trigger("tap",[event.target]),options.tap&&(ret=options.tap.call($element,event,event.target))},this),options.doubleTapThreshold)):(doubleTapStartTime=null,$element.trigger("tap",[event.target]),options.tap&&(ret=options.tap.call($element,event,event.target)))):gesture==DOUBLE_TAP?phase!==PHASE_CANCEL&&phase!==PHASE_END||(clearTimeout(singleTapTimeout),clearTimeout(holdTimeout),doubleTapStartTime=null,$element.trigger("doubletap",[event.target]),options.doubleTap&&(ret=options.doubleTap.call($element,event,event.target))):gesture==LONG_TAP&&(phase!==PHASE_CANCEL&&phase!==PHASE_END||(clearTimeout(singleTapTimeout),doubleTapStartTime=null,$element.trigger("longtap",[event.target]),options.longTap&&(ret=options.longTap.call($element,event,event.target)))),ret}function validateSwipeDistance(){var valid=!0;return null!==options.threshold&&(valid=distance>=options.threshold),valid}function didSwipeBackToCancel(){var cancelled=!1;return null!==options.cancelThreshold&&null!==direction&&(cancelled=getMaxDistance(direction)-distance>=options.cancelThreshold),cancelled}function validatePinchDistance(){return null!==options.pinchThreshold?pinchDistance>=options.pinchThreshold:!0}function validateSwipeTime(){var result;return result=options.maxTimeThreshold?!(duration>=options.maxTimeThreshold):!0}function validateDefaultEvent(jqEvent,direction){if(options.preventDefaultEvents!==!1)if(options.allowPageScroll===NONE)jqEvent.preventDefault();else{var auto=options.allowPageScroll===AUTO;switch(direction){case LEFT:(options.swipeLeft&&auto||!auto&&options.allowPageScroll!=HORIZONTAL)&&jqEvent.preventDefault();break;case RIGHT:(options.swipeRight&&auto||!auto&&options.allowPageScroll!=HORIZONTAL)&&jqEvent.preventDefault();break;case UP:(options.swipeUp&&auto||!auto&&options.allowPageScroll!=VERTICAL)&&jqEvent.preventDefault();break;case DOWN:(options.swipeDown&&auto||!auto&&options.allowPageScroll!=VERTICAL)&&jqEvent.preventDefault();break;case NONE:}}}function validatePinch(){var hasCorrectFingerCount=validateFingers(),hasEndPoint=validateEndPoint(),hasCorrectDistance=validatePinchDistance();return hasCorrectFingerCount&&hasEndPoint&&hasCorrectDistance}function hasPinches(){return!!(options.pinchStatus||options.pinchIn||options.pinchOut)}function didPinch(){return!(!validatePinch()||!hasPinches())}function validateSwipe(){var hasValidTime=validateSwipeTime(),hasValidDistance=validateSwipeDistance(),hasCorrectFingerCount=validateFingers(),hasEndPoint=validateEndPoint(),didCancel=didSwipeBackToCancel(),valid=!didCancel&&hasEndPoint&&hasCorrectFingerCount&&hasValidDistance&&hasValidTime;return valid}function hasSwipes(){return!!(options.swipe||options.swipeStatus||options.swipeLeft||options.swipeRight||options.swipeUp||options.swipeDown)}function didSwipe(){return!(!validateSwipe()||!hasSwipes())}function validateFingers(){return fingerCount===options.fingers||options.fingers===ALL_FINGERS||!SUPPORTS_TOUCH}function validateEndPoint(){return 0!==fingerData[0].end.x}function hasTap(){return!!options.tap}function hasDoubleTap(){return!!options.doubleTap}function hasLongTap(){return!!options.longTap}function validateDoubleTap(){if(null==doubleTapStartTime)return!1;var now=getTimeStamp();return hasDoubleTap()&&now-doubleTapStartTime<=options.doubleTapThreshold}function inDoubleTap(){return validateDoubleTap()}function validateTap(){return(1===fingerCount||!SUPPORTS_TOUCH)&&(isNaN(distance)||distanceoptions.longTapThreshold&&DOUBLE_TAP_THRESHOLD>distance}function didTap(){return!(!validateTap()||!hasTap())}function didDoubleTap(){return!(!validateDoubleTap()||!hasDoubleTap())}function didLongTap(){return!(!validateLongTap()||!hasLongTap())}function startMultiFingerRelease(event){previousTouchEndTime=getTimeStamp(),fingerCountAtRelease=event.touches.length+1}function cancelMultiFingerRelease(){previousTouchEndTime=0,fingerCountAtRelease=0}function inMultiFingerRelease(){var withinThreshold=!1;if(previousTouchEndTime){var diff=getTimeStamp()-previousTouchEndTime;diff<=options.fingerReleaseThreshold&&(withinThreshold=!0)}return withinThreshold}function getTouchInProgress(){return!($element.data(PLUGIN_NS+"_intouch")!==!0)}function setTouchInProgress(val){$element&&(val===!0?($element.bind(MOVE_EV,touchMove),$element.bind(END_EV,touchEnd),LEAVE_EV&&$element.bind(LEAVE_EV,touchLeave)):($element.unbind(MOVE_EV,touchMove,!1),$element.unbind(END_EV,touchEnd,!1),LEAVE_EV&&$element.unbind(LEAVE_EV,touchLeave,!1)),$element.data(PLUGIN_NS+"_intouch",val===!0))}function createFingerData(id,evt){var f={start:{x:0,y:0},last:{x:0,y:0},end:{x:0,y:0}};return f.start.x=f.last.x=f.end.x=evt.pageX||evt.clientX,f.start.y=f.last.y=f.end.y=evt.pageY||evt.clientY,fingerData[id]=f,f}function updateFingerData(evt){var id=void 0!==evt.identifier?evt.identifier:0,f=getFingerData(id);return null===f&&(f=createFingerData(id,evt)),f.last.x=f.end.x,f.last.y=f.end.y,f.end.x=evt.pageX||evt.clientX,f.end.y=evt.pageY||evt.clientY,f}function getFingerData(id){return fingerData[id]||null}function setMaxDistance(direction,distance){direction!=NONE&&(distance=Math.max(distance,getMaxDistance(direction)),maximumsMap[direction].distance=distance)}function getMaxDistance(direction){return maximumsMap[direction]?maximumsMap[direction].distance:void 0}function createMaximumsData(){var maxData={};return maxData[LEFT]=createMaximumVO(LEFT),maxData[RIGHT]=createMaximumVO(RIGHT),maxData[UP]=createMaximumVO(UP),maxData[DOWN]=createMaximumVO(DOWN),maxData}function createMaximumVO(dir){return{direction:dir,distance:0}}function calculateDuration(){return endTime-startTime}function calculateTouchesDistance(startPoint,endPoint){var diffX=Math.abs(startPoint.x-endPoint.x),diffY=Math.abs(startPoint.y-endPoint.y);return Math.round(Math.sqrt(diffX*diffX+diffY*diffY))}function calculatePinchZoom(startDistance,endDistance){var percent=endDistance/startDistance*1;return percent.toFixed(2)}function calculatePinchDirection(){return 1>pinchZoom?OUT:IN}function calculateDistance(startPoint,endPoint){return Math.round(Math.sqrt(Math.pow(endPoint.x-startPoint.x,2)+Math.pow(endPoint.y-startPoint.y,2)))}function calculateAngle(startPoint,endPoint){var x=startPoint.x-endPoint.x,y=endPoint.y-startPoint.y,r=Math.atan2(y,x),angle=Math.round(180*r/Math.PI);return 0>angle&&(angle=360-Math.abs(angle)),angle}function calculateDirection(startPoint,endPoint){if(comparePoints(startPoint,endPoint))return NONE;var angle=calculateAngle(startPoint,endPoint);return 45>=angle&&angle>=0?LEFT:360>=angle&&angle>=315?LEFT:angle>=135&&225>=angle?RIGHT:angle>45&&135>angle?DOWN:UP}function getTimeStamp(){var now=new Date;return now.getTime()}function getbounds(el){el=$(el);var offset=el.offset(),bounds={left:offset.left,right:offset.left+el.outerWidth(),top:offset.top,bottom:offset.top+el.outerHeight()};return bounds}function isInBounds(point,bounds){return point.x>bounds.left&&point.xbounds.top&&point.y 640 && width !== 768) { 42 | docEle.style.fontSize = "100px"; 43 | } else if (width == 320) { 44 | //iphone5 45 | docEle.style.fontSize = "49px"; 46 | } else if (width == 375) { 47 | //iphone6 48 | docEle.style.fontSize = "57px"; 49 | } else if (width == 414) { 50 | // iphone6s Plus 51 | docEle.style.fontSize = "57px"; 52 | } else if (width == 768) { 53 | //ipad 54 | docEle.style.fontSize = "88px"; 55 | } else { 56 | docEle.style.fontSize = Math.round(width / 650 * 100) + "px"; 57 | } 58 | } 59 | 60 | // 为歌曲列表添加动画延迟 61 | function addAnimationDelay(list, num) { 62 | var baseDelay = 0.1; 63 | list.each(function (index, ele) { 64 | if (num && index == num) { 65 | baseDelay = 0.1; 66 | } 67 | $(ele).css({ 68 | "animation-delay": baseDelay + "s" 69 | }); 70 | baseDelay += 0.1; 71 | }); 72 | } 73 | 74 | // 格式化时间为分:秒的形式 75 | function formatTime(seconds, curS) { 76 | var totalS = parseInt(seconds); 77 | var minute = Math.floor((totalS / 60)); 78 | var second = totalS - minute * 60; 79 | second = second < 10 ? ("0" + second) : second; 80 | 81 | return minute + ":" + second; 82 | } 83 | 84 | // 将时间转化为百分比 85 | function timeToPercent(curS, totalS) { 86 | var percent = parseInt((Number(curS) / Number(totalS)) * 100) + "%"; 87 | return percent; 88 | } 89 | 90 | // 更新时间 91 | function updataTime(dom, seconds) { 92 | var result = formatTime(seconds); 93 | dom.html(result); 94 | } 95 | 96 | // 更新进度条 97 | function updateProgress(dom, percent) { 98 | dom.css("width", percent); 99 | return true; 100 | } 101 | 102 | // 更新进度滑块 103 | function updateBarPos(dom, percent) { 104 | dom.css("left", percent); 105 | return true; 106 | } 107 | //生成大于等于0小于length的随机整数,且不为num 108 | function makeRandom(num, length) { 109 | var randomNum = 0; 110 | do { 111 | randomNum = Math.floor(Math.random() * length); 112 | } 113 | while (randomNum === num); 114 | return randomNum; 115 | } 116 | 117 | function setItem(key, value) { 118 | if (typeof value == "object") { 119 | return localStorage.setItem("h5player-" + key, JSON.stringify(value)); 120 | } 121 | return localStorage.setItem("h5player-" + key, value); 122 | } 123 | 124 | function getItem(key) { 125 | if (typeof localStorage.getItem("h5player-" + key) == "object") { 126 | return JSON.parse(localStorage.getItem("h5player-" + key)); 127 | } 128 | return localStorage.getItem("h5player-" + key); 129 | } 130 | // 格式化歌词函数--将歌词字符串分割成时间数组-及其对应的-歌词数组 131 | function createArrMap(lyric) { 132 | var timeArr = [], 133 | lyricArr = []; 134 | var tempArr = lyric.split("\n"); 135 | tempArr.splice(-1, 1); 136 | var tempStr = ""; 137 | $(tempArr).each(function (index) { 138 | tempStr = this; 139 | if (tempStr.charAt(9).match(/\d/) !== null) { 140 | tempStr = tempStr.substring(0, 9) + tempStr.substring(10); 141 | } 142 | timeArr.push(tempStr.substring(0, 10)); 143 | lyricArr.push(tempStr.substring(10)); 144 | }); 145 | return { 146 | timeArr: timeArr, 147 | lyricArr: lyricArr 148 | }; 149 | } 150 | // 格式化歌词时间为秒数 151 | function formatLyricTime(timeArr) { 152 | var result = []; 153 | var time = 0; 154 | var m = 0; 155 | var s = 0; 156 | $.each(timeArr, function (index) { 157 | time = this.replace(/[\[]|]|\s|:/ig, ""); 158 | m = +time.substring(0, 2); 159 | s = +time.substring(2); 160 | result.push(Math.floor(m * 60 + s)); 161 | 162 | }); 163 | return result; 164 | } 165 | // 获取歌词需要移动的距离--topLrcIndex 为容器视图可见的顶部歌词索引 166 | function getMoveDis(topLrcIndex) { 167 | var moveDis = (+Dom.lrc.eq(topLrcIndex).css("margin-top").replace("px", "")) + (+Dom.lrc.eq(topLrcIndex).height()); 168 | return moveDis; 169 | } 170 | // 获取时间变化后高亮歌词的索引 171 | function getHighLightIndex(curS, formatTimeArr) { 172 | var curSInt = Math.floor(curS); 173 | var highLightIndex = 0; 174 | var nextIndex = 0; 175 | // console.log(curSInt + "|" + formatTimeArr); 176 | for (var i = 0; i < formatTimeArr.length; i++) { 177 | nextIndex = i + 1; 178 | if (curSInt >= formatTimeArr[i] && curSInt <= formatTimeArr[nextIndex]) { 179 | highLightIndex = i; 180 | break; 181 | } 182 | if (curSInt >= formatTimeArr[i] && !formatTimeArr[nextIndex]) { 183 | highLightIndex = formatTimeArr.length - 1; 184 | break; 185 | } 186 | } 187 | return highLightIndex; 188 | } 189 | // 判断歌曲列表滑动到底部的函数 190 | function isScrollToBottom($dom, boxWinH, scrollTop) { 191 | var boxH = $dom.height(); 192 | var dis = (boxH - boxWinH) * 0.5; 193 | // console.log(scrollTop + "|" + dis); 194 | return scrollTop >= dis ? true : false; 195 | } 196 | return { 197 | rescale: rescale, 198 | addAnimationDelay: addAnimationDelay, 199 | formatTime: formatTime, 200 | updataTime: updataTime, 201 | updateProgress: updateProgress, 202 | updateBarPos: updateBarPos, 203 | timeToPercent: timeToPercent, 204 | makeRandom: makeRandom, 205 | setItem: setItem, 206 | getItem: getItem, 207 | createArrMap: createArrMap, 208 | formatLyricTime: formatLyricTime, 209 | getMoveDis: getMoveDis, 210 | getHighLightIndex: getHighLightIndex, 211 | isScrollToBottom: isScrollToBottom, 212 | 213 | }; 214 | })(); 215 | 216 | 217 | 218 | var Dom = { 219 | index: $("#index"), 220 | 221 | playPage: $(".play"), 222 | iconPMode: $(".m-playoptions .icon"), 223 | 224 | audio: $("#audio")[0], //Dom节点 225 | 226 | navBox: $("#index .m-nav"), 227 | 228 | 229 | songContainerWrap: $(".g-songlist"), 230 | sliderWrap: $(".sliderWrap"), 231 | songListContainer: $("#songlist"), 232 | lSongListContainer: $("#lsonglist"), 233 | song: $("#songlist .song"), 234 | lsong: $("#lsonglist .song"), 235 | 236 | psongname: $("#psongname"), 237 | psinger: $("#psinger"), 238 | lrcbox: $("#lrcbox"), 239 | lrcwrap: $("#lrc-wrap"), 240 | lrc: $("#lrc-wrap p"), 241 | lrcbg: $("#lrc-bg"), 242 | lrcimg: $("#lrcimg"), 243 | 244 | btnBack: $(".play .back"), 245 | btnControl: $(".btn-control"), 246 | btnPlay: $(".btn-play"), 247 | songCount: $("#totalsong"), 248 | 249 | start: $("#start"), 250 | end: $("#end"), 251 | 252 | msProgress: $(".m-progress-song"), // 歌曲进度条容器 253 | sProgress: $("#songprogress"), 254 | songbar: $("#songbar"), 255 | mvProgress: $(".m-progress-volume"), // 歌曲声音控制容器 256 | vProgress: $("#volprogress"), 257 | vbar: $("#volbar"), 258 | 259 | prev: $(".btn-prev"), 260 | next: $(".btn-next"), 261 | 262 | random: $(".icon-random"), 263 | menu: $(".icon-menu"), 264 | loop: $(".icon-loop"), 265 | 266 | footer: $(".g-footer") 267 | 268 | 269 | }; 270 | var winH = $(window).height(); 271 | 272 | var songNum = 0; //当前列表歌曲数目 273 | var lrcHighIndex = 0; // 歌词高亮索引 274 | var lrcMoveIndex = 0; // 歌词移动单位索引 275 | var moveDis = 0; // 单句歌词每次移动距离 276 | 277 | var duration = 0; // 当前歌曲的时间 278 | var index = 0; //当前播放歌曲的索引 279 | var songInfo = null; // 当前歌曲信息 280 | var songModelUI = null; // 当前歌曲UI模型 281 | var timeArr = []; //当前歌曲时间数组 282 | var formatTimeArr = []; //当前歌曲时间数组(格式化为秒数) 283 | 284 | Util.rescale(); 285 | 286 | // 入口函数 287 | function main() { 288 | initUIFrame(); 289 | var initModel = PlayerModel(); 290 | 291 | var songListUI = ModelUIFrame(Dom.songListContainer); 292 | var lsongListUI = ModelUIFrame(Dom.lSongListContainer); 293 | 294 | initModel.getSongList("data/data.json", function (data) { 295 | // 生成所有歌曲列表 296 | songListUI.renderList(data, 0, null, function () { 297 | songListUI.updateList(); 298 | }); 299 | 300 | // 生成喜爱歌曲列表 301 | initModel.getLoveSongArr(function (lSongArr) { 302 | lsongListUI.renderList(data, 1, lSongArr); 303 | }); 304 | 305 | // 添加动画 306 | Util.addAnimationDelay(Dom.song); 307 | 308 | // 保存歌词数据 309 | initModel.saveLyric(data); 310 | 311 | }); 312 | 313 | 314 | 315 | EventHandler(); 316 | } 317 | 318 | // =================================================================初始化UI模块 319 | function initUIFrame() { 320 | typeof (Settings.playmode) !== "undefined" && Dom.iconPMode.eq(Settings.playmode).addClass('active'); 321 | typeof (Settings.volume) !== "undefined" && Util.updateProgress(Dom.vProgress, (Settings.volume * 100) + "%") && Util.updateBarPos(Dom.vbar, (Settings.volume * 100) + "%") && (Dom.audio.volume = Settings.volume); 322 | } 323 | 324 | // =================================================================实现数据交互的方法模块 325 | function PlayerModel() { 326 | function getSongList(url, callback, reqData) { 327 | $.ajax({ 328 | url: url, 329 | type: 'GET', 330 | data: reqData || "", 331 | }) 332 | .done(function (data) { 333 | callback && callback(data); 334 | }) 335 | .fail(function () { 336 | console.log("error"); 337 | }) 338 | .always(function () { 339 | console.log("complete"); 340 | }); 341 | } 342 | 343 | function saveLyric(data) { 344 | $.each(data, function (index, item) { 345 | //jQuery实现break,使用return false;continue,使用return true. 346 | if (Util.getItem("lyric" + index) !== null) { 347 | return; 348 | } 349 | Util.setItem("lyric" + index, item.lyric); 350 | }); 351 | } 352 | 353 | function getLoveSongArr(callback) { 354 | var lSongArr = Util.getItem("lsonglist"); 355 | callback && callback(lSongArr); 356 | } 357 | return { 358 | getSongList: getSongList, 359 | saveLyric: saveLyric, 360 | getLoveSongArr: getLoveSongArr 361 | }; 362 | } 363 | 364 | // =================================================================模型动态UI模块 365 | function ModelUIFrame(container) { 366 | // Handlerbar 模板 367 | var sListTpl = $("#sListTpl").html(); 368 | var lyricTpl = $("#lyricTpl").html(); 369 | /** 370 | * 生成歌曲列表信息 371 | * @param {[Arr]} data [歌曲数据列表] 372 | * @param {[Number]} type [列表类型] 0:所有歌曲 1:喜爱歌曲 2:搜索歌曲 373 | * @param {Function} callback [回调] 374 | */ 375 | function renderList(data, type, lsongArr, callback) { 376 | // 生成所有歌曲列表 377 | function renderAllList(data) { 378 | var preTpl; 379 | var lsongArr = Util.getItem('lsonglist') === null ? [] : JSON.parse(Util.getItem('lsonglist')); 380 | // 生成列表 381 | if (!sListTpl) { 382 | // 后续动态生成歌曲 383 | var tpl = ""; 384 | var songIndex = songNum; 385 | $.each(data, function (index, el) { 386 | if (index >= songIndex && index < songIndex + Settings.reqNum) { 387 | tpl += "
  • " + el.songName + "

    " + el.singer + "
  • "; 388 | songNum++; 389 | } 390 | }); 391 | $(container).append($(tpl)); 392 | } else { 393 | // 首次生成歌曲 394 | preTpl = Handlebars.compile(sListTpl); 395 | $(container).html(preTpl(data)); 396 | } 397 | // 更新喜爱图标 398 | if (lsongArr.length !== 0) { 399 | $.each(lsongArr, function (index, val) { 400 | Dom.songListContainer.find(".song").eq(val).find(".icon-love").addClass('active'); 401 | }); 402 | } 403 | } 404 | // 生成喜爱歌曲列表 405 | function renderLSongList(data, lsongArr) { 406 | var lSongArr = lsongArr; 407 | var tpl = ""; 408 | if (typeof lSongArr !== "object") { 409 | lSongArr = JSON.parse(lsongArr); 410 | lSongArr.sort(function (a, b) { 411 | return a - b; 412 | }); 413 | } 414 | if (lSongArr && lSongArr.length !== 0) { 415 | Dom.lSongListContainer.html(""); 416 | $.each(data, function (index, el) { 417 | if (lSongArr[0] === index) { 418 | tpl += "
  • " + el.songName + "

    " + el.singer + "
  • "; 419 | lSongArr.shift(); 420 | } 421 | }); 422 | $(container).append($(tpl)); 423 | } 424 | Dom.lsong = $("#lsonglist .song"); 425 | } 426 | (type === 0) && renderAllList(data); 427 | (type === 1) && renderLSongList(data, lsongArr); 428 | 429 | callback && callback(); 430 | } 431 | // 更新歌曲列表信息 432 | function updateList() { 433 | Dom.song = $("#songlist .song"); 434 | songNum = Dom.song.length; 435 | $("#totalsong").html(songNum); 436 | } 437 | // 生词歌曲信息(包括歌名|歌手名|歌词) 438 | function updateSongInfo(songinfo) { 439 | Dom.psongname.text(songinfo.songname); 440 | Dom.psinger.text(songinfo.singer); 441 | renderLyric(songinfo); 442 | Dom.lrcwrap.css({ 443 | "top": 0 444 | }); 445 | Dom.lrcimg.attr("src", songinfo.lrcimg); 446 | Dom.lrc = Dom.lrcwrap.find("p"); 447 | } 448 | // 生成歌词 449 | function renderLyric(songinfo) { 450 | var arrMap = Util.createArrMap(songinfo.lyric); 451 | if (!lyricTpl) { 452 | var tpl = ""; 453 | $.each(arrMap.lyricArr, function (index, lyric) { 454 | var lyricContent = lyric === "" ? "--------------" : lyric; 455 | tpl += "

    " + lyricContent + "

    "; 456 | }); 457 | Dom.lrcwrap.html(tpl); 458 | return; 459 | } 460 | var preTpl = Handlebars.compile(lyricTpl); 461 | Dom.lrcwrap.html(preTpl(arrMap)); 462 | } 463 | 464 | // 歌词同步 465 | function syncLyric(curS, formatTimeArr) { 466 | // console.log(Math.floor(curS) + "|" + lrcHighIndex); 467 | if (Math.floor(curS) >= formatTimeArr[lrcHighIndex]) { 468 | Dom.lrc.eq(lrcHighIndex).addClass('current').siblings().removeClass('current'); 469 | if (lrcHighIndex >= 1) { 470 | lrcMoveIndex = lrcHighIndex - 2; 471 | moveDis += Util.getMoveDis(lrcMoveIndex); 472 | Dom.lrcwrap.animate({ 473 | "top": "-" + moveDis + "px" 474 | }, 100); 475 | lrcMoveIndex++; 476 | } 477 | lrcHighIndex++; 478 | } 479 | } 480 | 481 | function updateLrcView(curS, formatTimeArr) { 482 | moveDis = 0; 483 | lrcHighIndex = Util.getHighLightIndex(curS, formatTimeArr); 484 | lrcMoveIndex = lrcHighIndex - 2; 485 | Dom.lrc.eq(lrcHighIndex).addClass('current').siblings().removeClass('current'); 486 | for (var i = 0; i < lrcHighIndex - 1; i++) { 487 | moveDis += Util.getMoveDis(i); 488 | } 489 | Dom.lrcwrap.animate({ 490 | "top": "-" + moveDis + "px" 491 | }, 100); 492 | 493 | } 494 | return { 495 | renderList: renderList, 496 | updateList: updateList, 497 | updateSongInfo: updateSongInfo, 498 | syncLyric: syncLyric, 499 | updateLrcView: updateLrcView, 500 | }; 501 | } 502 | 503 | // =================================================================事件绑定模块 504 | function EventHandler() { 505 | 506 | // 根据手机屏幕调整布局 507 | $(window).on("resize", function () { 508 | Util.rescale(); 509 | }); 510 | 511 | // 滚动加载 512 | Dom.sliderWrap.eq(0).on("scroll", function (e) { 513 | var updateSongModel = PlayerModel(); 514 | var updateSongListUI = ModelUIFrame(Dom.songListContainer); 515 | var scrT = $(this).scrollTop(); 516 | var listBoxWinH = Dom.songContainerWrap.height(); 517 | 518 | if (Util.isScrollToBottom($(this), listBoxWinH, scrT)) { 519 | updateSongModel.getSongList("data/data.json", function (data) { 520 | // 生成歌曲列表 521 | var beUpdSongNum = songNum; // 记录更新前歌曲的总数 522 | updateSongListUI.renderList(data, 0, null, function () { 523 | updateSongListUI.updateList(); 524 | }); 525 | Util.addAnimationDelay(Dom.song, beUpdSongNum); 526 | // 保存歌词数据 527 | updateSongModel.saveLyric(data); 528 | }); 529 | } 530 | }); 531 | 532 | // 歌曲播放完毕事件 533 | Dom.audio.onended = function () { 534 | // 触发点击下一首歌的事件 535 | $(Dom.next).trigger("click"); 536 | }; 537 | 538 | // 监听歌曲播放时间发生变化事件 539 | $(Dom.audio).on("timeupdate", function () { 540 | var curS = Dom.audio.currentTime; 541 | var curPercent = Util.timeToPercent(curS, duration); 542 | 543 | // 歌词同步 544 | songModelUI.syncLyric(curS, formatTimeArr); 545 | 546 | // 更新歌曲时间,进度条 547 | Util.updataTime(Dom.start, curS); 548 | Util.updateProgress(Dom.sProgress, curPercent); 549 | Util.updateBarPos(Dom.songbar, curPercent); 550 | }); 551 | 552 | // 点击一首歌曲 553 | $(".g-songlist").on("click", ".poster", function (e) { 554 | e.preventDefault(); 555 | e.stopPropagation(); 556 | 557 | // 列表视图相关 558 | var listType = Dom.navBox.find(".nav.active").index(); 559 | if (listType === 0) { 560 | Dom.songContainerWrap.find(".m-songlist").eq(1).find(".song").removeClass('active'); 561 | } else if (listType === 1) { 562 | Dom.songContainerWrap.find(".m-songlist").eq(0).find(".song").removeClass('active'); 563 | } 564 | 565 | $(this).parents(".song").addClass("active").siblings().removeClass("active"); 566 | Dom.index.addClass('hide'); 567 | 568 | Dom.playPage.css({ 569 | transform: "translateY(0%)" 570 | }); 571 | 572 | // 音频相关 573 | if (Dom.audio.src == rootPath + $(this).parents(".song").data("src")) { 574 | return; 575 | } 576 | Dom.audio.src = $(this).parents(".song").data("src"); 577 | Dom.audio.play(); 578 | Dom.btnPlay.removeClass("btn-play").addClass("btn-pause"); 579 | 580 | Dom.audio.setAttribute("index", $(this).parents(".song").data("index")); 581 | index = $(this).parents(".song").data("index"); 582 | 583 | Dom.audio.oncanplay = function () { 584 | duration = this.duration; 585 | formatDuration = Util.formatTime(duration); 586 | Dom.end.html(formatDuration); 587 | 588 | }; 589 | 590 | // 播放视图相关 591 | songInfo = { 592 | songname: $(this).parents(".song").find(".lsongname").text(), 593 | singer: $(this).parents(".song").find(".lsinger").text(), 594 | lrcimg: "img/poster/" + $(this).parents(".song").data("index") + "-origin.jpg", 595 | lyric: Util.getItem("lyric" + $(this).parents(".song").data("index")) 596 | }; 597 | timeArr = Util.createArrMap(songInfo.lyric).timeArr; 598 | formatTimeArr = Util.formatLyricTime(timeArr); 599 | songModelUI = ModelUIFrame(); 600 | songModelUI.updateSongInfo(songInfo); 601 | 602 | lrcMoveIndex = 0; 603 | lrcHighIndex = 0; 604 | moveDis = 0; 605 | }); 606 | 607 | // 点击后退按钮 608 | Dom.btnBack.on("click", function (e) { 609 | e.preventDefault(); 610 | e.stopPropagation(); 611 | Dom.index.removeClass('hide'); 612 | Dom.playPage.css({ 613 | transform: "translateY(-100%)" 614 | }); 615 | }); 616 | 617 | function saveLoveSong() { 618 | var cIndex = $(this).parents(".song").data("index"); // 当前点击歌曲索引 619 | var loveFlag = $(this).find(".icon-love").hasClass('active'); 620 | var lsongArr = Util.getItem('lsonglist') === null ? [] : Util.getItem('lsonglist'); // 记录喜爱歌曲索引的数组 621 | 622 | if (loveFlag) { 623 | if (lsongArr.length === 0) { 624 | return; 625 | } else { 626 | lsongArr = JSON.parse(lsongArr); 627 | $.each(lsongArr, function (index, value) { 628 | if (value === cIndex) { 629 | lsongArr.splice(index, 1); 630 | Util.setItem("lsonglist", lsongArr); 631 | return; 632 | } 633 | }); 634 | } 635 | } else { 636 | if (lsongArr.length === 0) { 637 | lsongArr.push(cIndex); 638 | } else { 639 | lsongArr = JSON.parse(lsongArr); 640 | lsongArr.push(cIndex); 641 | } 642 | Util.setItem("lsonglist", lsongArr); 643 | } 644 | } 645 | // 点击喜爱按钮 646 | $(".g-songlist").on("click", ".loveflag", function (e) { 647 | e.preventDefault(); 648 | e.stopPropagation(); 649 | 650 | saveLoveSong.call(this); 651 | $(this).find(".icon-love").toggleClass('active'); 652 | }); 653 | 654 | // 播放或暂停按钮 655 | Dom.btnControl.on("click", function () { 656 | if ($(this).hasClass("btn-play")) { 657 | Dom.audio.play(); 658 | $(this).removeClass('btn-play').addClass('btn-pause'); 659 | } else { 660 | Dom.audio.pause(); 661 | $(this).removeClass('btn-pause').addClass('btn-play'); 662 | } 663 | }); 664 | /** 665 | * @param {[Number]} listType [0:歌曲列表,1:我的最爱] 666 | * @param {[String]} 点击按钮类型 ["prev":上一首 "next":下一首] 667 | * @param {[String]} 播放模式 668 | * @return {[Number]} [返回下一首播放的index] 669 | */ 670 | function getNextIndex(listType, clickType, playmode) { 671 | var activeIndex; 672 | var nextIndex; 673 | // 单击类型:下一首 674 | if (clickType === "prev") { 675 | if (listType === 0) { 676 | switch (playmode) { 677 | case 0: 678 | nextIndex = (index - 1) < 0 ? songNum - 1 : index - 1; 679 | break; 680 | case 1: 681 | nextIndex = Util.makeRandom(index, songNum); 682 | break; 683 | case 2: 684 | nextIndex = index; 685 | break; 686 | } 687 | } else if (listType === 1) { 688 | activeIndex = Dom.lSongListContainer.find(".song.active").index(); 689 | switch (playmode) { 690 | case 0: 691 | nextIndex = (activeIndex - 1) < 0 ? songNum - 1 : activeIndex - 1; 692 | break; 693 | case 1: 694 | nextIndex = Util.makeRandom(activeIndex, songNum); 695 | break; 696 | case 2: 697 | nextIndex = activeIndex; 698 | break; 699 | } 700 | } 701 | 702 | } else if (clickType === "next") { // 单击类型:下一首 703 | if (listType === 0) { 704 | switch (playmode) { 705 | case 0: 706 | nextIndex = (index + 1) > songNum - 1 ? 0 : index + 1; 707 | break; 708 | case 1: 709 | nextIndex = Util.makeRandom(index, songNum); 710 | break; 711 | case 2: 712 | nextIndex = index; 713 | break; 714 | } 715 | } else if (listType === 1) { 716 | 717 | activeIndex = Dom.lSongListContainer.find(".song.active").index(); 718 | 719 | switch (playmode) { 720 | case 0: 721 | nextIndex = (activeIndex + 1) > songNum - 1 ? 0 : activeIndex + 1; 722 | break; 723 | case 1: 724 | nextIndex = Util.makeRandom(activeIndex, songNum); 725 | break; 726 | case 2: 727 | nextIndex = activeIndex; 728 | break; 729 | } 730 | } 731 | } 732 | return nextIndex; 733 | } 734 | 735 | // 上一首 736 | var src = ""; // 记录歌曲地址变量 737 | var lrcIndex; // 歌词本地查找索引:歌曲列表中lrcIndex = index;我的最爱中lrcIndex可能和index不相等 738 | var listType; // 当前所处列表类型: 0为歌曲列表 1为我的最爱 739 | Dom.prev.on("click", function () { 740 | listType = Dom.navBox.find(".nav.active").index(); 741 | index = getNextIndex(listType, "prev", Settings.playmode); 742 | 743 | if (listType === 0) { 744 | Dom.song.eq(index).addClass("active").siblings().removeClass('active'); 745 | src = Dom.song.eq(index).data("src"); 746 | lrcIndex = Dom.song.eq(index).data("index"); 747 | } else if (listType === 1) { 748 | Dom.lsong.eq(index).addClass("active").siblings().removeClass('active'); 749 | src = Dom.lsong.eq(index).data("src"); 750 | lrcIndex = Dom.lsong.eq(index).data("index"); 751 | } 752 | Dom.audio.src = src; 753 | Dom.audio.play(); 754 | 755 | // 播放视图相关 756 | songInfo = { 757 | songname: Dom.song.eq(lrcIndex).find(".lsongname").text() || Dom.lsong.eq(index).find(".lsongname").text(), // 当播放的是“歌曲列表”未加载的歌曲时,使用“我的最爱”列表中的歌曲信息 758 | singer: Dom.song.eq(lrcIndex).find(".lsinger").text() || Dom.lsong.eq(index).find(".lsinger").text(), 759 | lrcimg: rootPath + "img/poster/" + lrcIndex + "-origin.jpg", 760 | lyric: Util.getItem("lyric" + lrcIndex) 761 | }; 762 | lrcMoveIndex = 0; 763 | lrcHighIndex = 0; 764 | moveDis = 0; 765 | 766 | timeArr = Util.createArrMap(songInfo.lyric).timeArr; 767 | formatTimeArr = Util.formatLyricTime(timeArr); 768 | songModelUI = ModelUIFrame(); 769 | songModelUI.updateSongInfo(songInfo); 770 | 771 | }); 772 | 773 | //下一首 774 | Dom.next.on("click", function () { 775 | 776 | listType = Dom.navBox.find(".nav.active").index(); 777 | index = getNextIndex(listType, "next", Settings.playmode); 778 | 779 | if (listType === 0) { 780 | Dom.song.eq(index).addClass("active").siblings().removeClass('active'); 781 | src = Dom.song.eq(index).data("src"); 782 | lrcIndex = Dom.song.eq(index).data("index"); 783 | 784 | } else if (listType === 1) { 785 | Dom.lsong.eq(index).addClass("active").siblings().removeClass('active'); 786 | src = Dom.lsong.eq(index).data("src"); 787 | lrcIndex = Dom.lsong.eq(index).data("index"); 788 | } 789 | Dom.audio.src = src; 790 | Dom.audio.play(); 791 | 792 | // 播放视图相关 793 | songInfo = { 794 | songname: Dom.song.eq(lrcIndex).find(".lsongname").text() || Dom.lsong.eq(index).find(".lsongname").text(), // 当播放的是“歌曲列表”未加载的歌曲时,使用“我的最爱”列表中的歌曲信息 795 | singer: Dom.song.eq(lrcIndex).find(".lsinger").text() || Dom.lsong.eq(index).find(".lsinger").text(), 796 | lrcimg: rootPath + "img/poster/" + lrcIndex + "-origin.jpg", 797 | lyric: Util.getItem("lyric" + lrcIndex) 798 | }; 799 | lrcMoveIndex = 0; 800 | lrcHighIndex = 0; 801 | moveDis = 0; 802 | 803 | timeArr = Util.createArrMap(songInfo.lyric).timeArr; 804 | formatTimeArr = Util.formatLyricTime(timeArr); 805 | songModelUI = ModelUIFrame(); 806 | songModelUI.updateSongInfo(songInfo); 807 | }); 808 | 809 | //随机播放 810 | Dom.random.on("click", function () { 811 | $(this).addClass("active").siblings().removeClass("active"); 812 | Settings.playmode = 1; 813 | }); 814 | 815 | //列表循环 816 | Dom.menu.on("click", function () { 817 | $(this).addClass("active").siblings().removeClass("active"); 818 | Settings.playmode = 0; 819 | }); 820 | 821 | //单曲循环播放 822 | Dom.loop.on("click", function () { 823 | $(this).addClass("active").siblings().removeClass("active"); 824 | Settings.playmode = 2; 825 | }); 826 | 827 | //歌曲进度条滑块滑动 828 | Dom.songbar.on("touchstart", function (e) { 829 | e.preventDefault(); 830 | e.stopPropagation(); 831 | 832 | $(Dom.audio).off("timeupdate"); 833 | Dom.audio.pause(); 834 | 835 | var totalW = $(Dom.msProgress).width(); 836 | var leftDis = $(Dom.sProgress).offset().left; 837 | var curS = 0; 838 | var curPercent = 0; 839 | var percent = ""; 840 | var touchMove = e.originalEvent.changedTouches[0].clientX; 841 | var dis = e.originalEvent.changedTouches[0].clientX - leftDis; 842 | Dom.songbar.on("touchmove", function (e) { 843 | e.preventDefault(); 844 | e.stopPropagation(); 845 | touchMove = e.originalEvent.targetTouches[0].clientX; 846 | dis = touchMove - leftDis > totalW ? totalW : touchMove - leftDis; 847 | dis = touchMove - leftDis < 0 ? 0 : dis; 848 | percent = Math.floor(dis / totalW * 100) + "%"; 849 | Util.updateProgress(Dom.sProgress, percent); 850 | Util.updateBarPos(Dom.songbar, percent); 851 | 852 | }); 853 | Dom.songbar.on("touchend", function (e) { 854 | e.preventDefault(); 855 | e.stopPropagation(); 856 | 857 | if (Dom.audio.paused) { 858 | Dom.audio.play(); 859 | } 860 | 861 | percent = Math.floor(dis / totalW * 100) + "%"; 862 | Util.updateProgress(Dom.sProgress, percent); 863 | Util.updateBarPos(Dom.songbar, percent); 864 | curS = duration * parseInt(percent.replace("%", "")) / 100; 865 | Dom.audio.currentTime = curS; 866 | 867 | // 播放视图相关 868 | songModelUI.updateLrcView(curS, formatTimeArr); 869 | Dom.audio.ontimeupdate = function () { 870 | var curS = Dom.audio.currentTime; 871 | var curPercent = Util.timeToPercent(curS, duration); 872 | 873 | // 歌词同步 874 | songModelUI.syncLyric(curS, formatTimeArr); 875 | 876 | // 更新歌曲时间,进度条 877 | Util.updataTime(Dom.start, curS); 878 | Util.updateProgress(Dom.sProgress, curPercent); 879 | Util.updateBarPos(Dom.songbar, curPercent); 880 | }; 881 | 882 | Dom.songbar.off("touchmove touchend"); 883 | }); 884 | }); 885 | 886 | // 歌曲进度条点击 887 | Dom.msProgress.on("mousedown", function (e) { 888 | $(Dom.audio).off("timeupdate"); 889 | 890 | var totalW = $(Dom.msProgress).width(); 891 | var leftDis = $(Dom.sProgress).offset().left; 892 | var curS = 0; 893 | var curPercent = 0; 894 | var dis = e.pageX - leftDis > totalW ? totalW : e.pageX - leftDis; 895 | percent = Math.floor(dis / totalW * 100) + "%"; 896 | 897 | Dom.msProgress.on("mouseup", function (e) { 898 | Util.updateProgress(Dom.sProgress, percent); 899 | Util.updateBarPos(Dom.songbar, percent); 900 | curS = duration * parseInt(percent.replace("%", "")) / 100; 901 | Dom.audio.currentTime = curS; 902 | 903 | // 播放视图相关 904 | songModelUI.updateLrcView(curS, formatTimeArr); 905 | Dom.audio.ontimeupdate = function () { 906 | var curS = Dom.audio.currentTime; 907 | var curPercent = Util.timeToPercent(curS, duration); 908 | 909 | // 歌词同步 910 | songModelUI.syncLyric(curS, formatTimeArr); 911 | 912 | // 更新歌曲时间,进度条 913 | Util.updataTime(Dom.start, curS); 914 | Util.updateProgress(Dom.sProgress, curPercent); 915 | Util.updateBarPos(Dom.songbar, curPercent); 916 | }; 917 | 918 | Dom.msProgress.off("mouseup"); 919 | }); 920 | 921 | }); 922 | 923 | //音量控制 924 | Dom.vbar.on("touchstart", function (e) { 925 | var totalW = $(Dom.mvProgress).width(); 926 | var leftDis = $(Dom.sProgress).offset().left; 927 | var percent = ""; 928 | e.preventDefault(); 929 | e.stopPropagation(); 930 | Dom.vbar.on("touchmove", function (e) { 931 | e.preventDefault(); 932 | e.stopPropagation(); 933 | var touchMove = e.originalEvent.targetTouches[0].clientX; 934 | var dis = touchMove - leftDis > totalW ? totalW : touchMove - leftDis; 935 | dis = touchMove - leftDis < 0 ? 0 : dis; 936 | percent = Math.floor(dis / totalW * 100) + "%"; 937 | Dom.audio.volume = (dis / totalW).toFixed(1); 938 | Util.updateProgress(Dom.vProgress, percent); 939 | Util.updateBarPos(Dom.vbar, percent); 940 | }); 941 | Dom.vbar.on("touchend", function (e) { 942 | e.preventDefault(); 943 | e.stopPropagation(); 944 | Dom.vbar.off("touchmove touchend"); 945 | }); 946 | }); 947 | 948 | // 歌词海报切换 949 | Dom.lrcbox.on("click", function () { 950 | $(this).find(".lrc-no").toggleClass("hide"); 951 | $(this).find(".lrc").toggleClass("hide"); 952 | }); 953 | 954 | // 歌曲列表向上或向下滑动 955 | var top = 0; 956 | Dom.sliderWrap.each(function () { 957 | $(this).on("scroll", function () { 958 | var tempTop = $(this).scrollTop(); 959 | if (tempTop - top > 10) { //防止弹性滚动造成的不是预期的效果 960 | // 向下 961 | Dom.footer.stop(true).animate({ 962 | bottom: "-100%" 963 | }, 1000); 964 | } else if (tempTop - top < -10) { 965 | // 向上 966 | Dom.footer.stop(true).animate({ 967 | bottom: "0%" 968 | }, 1000); 969 | } 970 | top = tempTop; 971 | }); 972 | }); 973 | 974 | // 列表切换(点击) 975 | $(".nav").on("click", function () { 976 | var navIndex = $(this).index(); 977 | songNum = Dom.songContainerWrap.find(".m-songlist").eq(navIndex).find(".song").length; 978 | // 切换 979 | $(this).addClass('active').siblings().removeClass('active'); 980 | Dom.songContainerWrap.css({ 981 | "transform": "translateX(" + (-navIndex * 1 / 3) * 100 + "%)" 982 | }); 983 | 984 | // 更新歌曲数据 985 | Dom.songCount.text(songNum); 986 | }); 987 | 988 | // 点击用户头像 989 | $(".icon-user").on("click", function () { 990 | $(".g-user").css({ 991 | transform: "translateX(0%)" 992 | }); 993 | }); 994 | 995 | // 点击用户头像页面 996 | $(".g-user").on("click", function () { 997 | $(".g-user").css({ 998 | transform: "translateX(100%)" 999 | }); 1000 | }); 1001 | 1002 | // 阻止默认滚动事件 1003 | $(window).on('scroll', function (e) { 1004 | console.log("window"); 1005 | e.preventDefault(); 1006 | e.stopPropagation(); 1007 | }); 1008 | $("body").on('scroll', function (e) { 1009 | e.preventDefault(); 1010 | e.stopPropagation(); 1011 | }); 1012 | $(".index").on('scroll', function (e) { 1013 | e.preventDefault(); 1014 | e.stopPropagation(); 1015 | }); 1016 | $(".g-header").on('scroll', function (e) { 1017 | console.log("g-header"); 1018 | e.preventDefault(); 1019 | e.stopPropagation(); 1020 | var navIndex = Dom.navBox.find(".nav.active").index(); 1021 | Dom.sliderWrap.eq(navIndex).trigger('scroll'); 1022 | }); 1023 | 1024 | 1025 | } 1026 | main(); 1027 | 1028 | }); -------------------------------------------------------------------------------- /lib/iscroll.js: -------------------------------------------------------------------------------- 1 | /*! iScroll v5.2.0 ~ (c) 2008-2016 Matteo Spinelli ~ http://cubiq.org/license */ 2 | (function (window, document, Math) { 3 | var rAF = window.requestAnimationFrame || 4 | window.webkitRequestAnimationFrame || 5 | window.mozRequestAnimationFrame || 6 | window.oRequestAnimationFrame || 7 | window.msRequestAnimationFrame || 8 | function (callback) { window.setTimeout(callback, 1000 / 60); }; 9 | 10 | var utils = (function () { 11 | var me = {}; 12 | 13 | var _elementStyle = document.createElement('div').style; 14 | var _vendor = (function () { 15 | var vendors = ['t', 'webkitT', 'MozT', 'msT', 'OT'], 16 | transform, 17 | i = 0, 18 | l = vendors.length; 19 | 20 | for ( ; i < l; i++ ) { 21 | transform = vendors[i] + 'ransform'; 22 | if ( transform in _elementStyle ) return vendors[i].substr(0, vendors[i].length-1); 23 | } 24 | 25 | return false; 26 | })(); 27 | 28 | function _prefixStyle (style) { 29 | if ( _vendor === false ) return false; 30 | if ( _vendor === '' ) return style; 31 | return _vendor + style.charAt(0).toUpperCase() + style.substr(1); 32 | } 33 | 34 | me.getTime = Date.now || function getTime () { return new Date().getTime(); }; 35 | 36 | me.extend = function (target, obj) { 37 | for ( var i in obj ) { 38 | target[i] = obj[i]; 39 | } 40 | }; 41 | 42 | me.addEvent = function (el, type, fn, capture) { 43 | el.addEventListener(type, fn, !!capture); 44 | }; 45 | 46 | me.removeEvent = function (el, type, fn, capture) { 47 | el.removeEventListener(type, fn, !!capture); 48 | }; 49 | 50 | me.prefixPointerEvent = function (pointerEvent) { 51 | return window.MSPointerEvent ? 52 | 'MSPointer' + pointerEvent.charAt(7).toUpperCase() + pointerEvent.substr(8): 53 | pointerEvent; 54 | }; 55 | 56 | me.momentum = function (current, start, time, lowerMargin, wrapperSize, deceleration) { 57 | var distance = current - start, 58 | speed = Math.abs(distance) / time, 59 | destination, 60 | duration; 61 | 62 | deceleration = deceleration === undefined ? 0.0006 : deceleration; 63 | 64 | destination = current + ( speed * speed ) / ( 2 * deceleration ) * ( distance < 0 ? -1 : 1 ); 65 | duration = speed / deceleration; 66 | 67 | if ( destination < lowerMargin ) { 68 | destination = wrapperSize ? lowerMargin - ( wrapperSize / 2.5 * ( speed / 8 ) ) : lowerMargin; 69 | distance = Math.abs(destination - current); 70 | duration = distance / speed; 71 | } else if ( destination > 0 ) { 72 | destination = wrapperSize ? wrapperSize / 2.5 * ( speed / 8 ) : 0; 73 | distance = Math.abs(current) + destination; 74 | duration = distance / speed; 75 | } 76 | 77 | return { 78 | destination: Math.round(destination), 79 | duration: duration 80 | }; 81 | }; 82 | 83 | var _transform = _prefixStyle('transform'); 84 | 85 | me.extend(me, { 86 | hasTransform: _transform !== false, 87 | hasPerspective: _prefixStyle('perspective') in _elementStyle, 88 | hasTouch: 'ontouchstart' in window, 89 | hasPointer: !!(window.PointerEvent || window.MSPointerEvent), // IE10 is prefixed 90 | hasTransition: _prefixStyle('transition') in _elementStyle 91 | }); 92 | 93 | /* 94 | This should find all Android browsers lower than build 535.19 (both stock browser and webview) 95 | - galaxy S2 is ok 96 | - 2.3.6 : `AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1` 97 | - 4.0.4 : `AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30` 98 | - galaxy S3 is badAndroid (stock brower, webview) 99 | `AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30` 100 | - galaxy S4 is badAndroid (stock brower, webview) 101 | `AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30` 102 | - galaxy S5 is OK 103 | `AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Mobile Safari/537.36 (Chrome/)` 104 | - galaxy S6 is OK 105 | `AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Mobile Safari/537.36 (Chrome/)` 106 | */ 107 | me.isBadAndroid = (function() { 108 | var appVersion = window.navigator.appVersion; 109 | // Android browser is not a chrome browser. 110 | if (/Android/.test(appVersion) && !(/Chrome\/\d/.test(appVersion))) { 111 | var safariVersion = appVersion.match(/Safari\/(\d+.\d)/); 112 | if(safariVersion && typeof safariVersion === "object" && safariVersion.length >= 2) { 113 | return parseFloat(safariVersion[1]) < 535.19; 114 | } else { 115 | return true; 116 | } 117 | } else { 118 | return false; 119 | } 120 | })(); 121 | 122 | me.extend(me.style = {}, { 123 | transform: _transform, 124 | transitionTimingFunction: _prefixStyle('transitionTimingFunction'), 125 | transitionDuration: _prefixStyle('transitionDuration'), 126 | transitionDelay: _prefixStyle('transitionDelay'), 127 | transformOrigin: _prefixStyle('transformOrigin') 128 | }); 129 | 130 | me.hasClass = function (e, c) { 131 | var re = new RegExp("(^|\\s)" + c + "(\\s|$)"); 132 | return re.test(e.className); 133 | }; 134 | 135 | me.addClass = function (e, c) { 136 | if ( me.hasClass(e, c) ) { 137 | return; 138 | } 139 | 140 | var newclass = e.className.split(' '); 141 | newclass.push(c); 142 | e.className = newclass.join(' '); 143 | }; 144 | 145 | me.removeClass = function (e, c) { 146 | if ( !me.hasClass(e, c) ) { 147 | return; 148 | } 149 | 150 | var re = new RegExp("(^|\\s)" + c + "(\\s|$)", 'g'); 151 | e.className = e.className.replace(re, ' '); 152 | }; 153 | 154 | me.offset = function (el) { 155 | var left = -el.offsetLeft, 156 | top = -el.offsetTop; 157 | 158 | // jshint -W084 159 | while (el = el.offsetParent) { 160 | left -= el.offsetLeft; 161 | top -= el.offsetTop; 162 | } 163 | // jshint +W084 164 | 165 | return { 166 | left: left, 167 | top: top 168 | }; 169 | }; 170 | 171 | me.preventDefaultException = function (el, exceptions) { 172 | for ( var i in exceptions ) { 173 | if ( exceptions[i].test(el[i]) ) { 174 | return true; 175 | } 176 | } 177 | 178 | return false; 179 | }; 180 | 181 | me.extend(me.eventType = {}, { 182 | touchstart: 1, 183 | touchmove: 1, 184 | touchend: 1, 185 | 186 | mousedown: 2, 187 | mousemove: 2, 188 | mouseup: 2, 189 | 190 | pointerdown: 3, 191 | pointermove: 3, 192 | pointerup: 3, 193 | 194 | MSPointerDown: 3, 195 | MSPointerMove: 3, 196 | MSPointerUp: 3 197 | }); 198 | 199 | me.extend(me.ease = {}, { 200 | quadratic: { 201 | style: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)', 202 | fn: function (k) { 203 | return k * ( 2 - k ); 204 | } 205 | }, 206 | circular: { 207 | style: 'cubic-bezier(0.1, 0.57, 0.1, 1)', // Not properly "circular" but this looks better, it should be (0.075, 0.82, 0.165, 1) 208 | fn: function (k) { 209 | return Math.sqrt( 1 - ( --k * k ) ); 210 | } 211 | }, 212 | back: { 213 | style: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)', 214 | fn: function (k) { 215 | var b = 4; 216 | return ( k = k - 1 ) * k * ( ( b + 1 ) * k + b ) + 1; 217 | } 218 | }, 219 | bounce: { 220 | style: '', 221 | fn: function (k) { 222 | if ( ( k /= 1 ) < ( 1 / 2.75 ) ) { 223 | return 7.5625 * k * k; 224 | } else if ( k < ( 2 / 2.75 ) ) { 225 | return 7.5625 * ( k -= ( 1.5 / 2.75 ) ) * k + 0.75; 226 | } else if ( k < ( 2.5 / 2.75 ) ) { 227 | return 7.5625 * ( k -= ( 2.25 / 2.75 ) ) * k + 0.9375; 228 | } else { 229 | return 7.5625 * ( k -= ( 2.625 / 2.75 ) ) * k + 0.984375; 230 | } 231 | } 232 | }, 233 | elastic: { 234 | style: '', 235 | fn: function (k) { 236 | var f = 0.22, 237 | e = 0.4; 238 | 239 | if ( k === 0 ) { return 0; } 240 | if ( k == 1 ) { return 1; } 241 | 242 | return ( e * Math.pow( 2, - 10 * k ) * Math.sin( ( k - f / 4 ) * ( 2 * Math.PI ) / f ) + 1 ); 243 | } 244 | } 245 | }); 246 | 247 | me.tap = function (e, eventName) { 248 | var ev = document.createEvent('Event'); 249 | ev.initEvent(eventName, true, true); 250 | ev.pageX = e.pageX; 251 | ev.pageY = e.pageY; 252 | e.target.dispatchEvent(ev); 253 | }; 254 | 255 | me.click = function (e) { 256 | var target = e.target, 257 | ev; 258 | 259 | if ( !(/(SELECT|INPUT|TEXTAREA)/i).test(target.tagName) ) { 260 | // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/initMouseEvent 261 | // initMouseEvent is deprecated. 262 | ev = document.createEvent(window.MouseEvent ? 'MouseEvents' : 'Event'); 263 | ev.initEvent('click', true, true); 264 | ev.view = e.view || window; 265 | ev.detail = 1; 266 | ev.screenX = target.screenX || 0; 267 | ev.screenY = target.screenY || 0; 268 | ev.clientX = target.clientX || 0; 269 | ev.clientY = target.clientY || 0; 270 | ev.ctrlKey = !!e.ctrlKey; 271 | ev.altKey = !!e.altKey; 272 | ev.shiftKey = !!e.shiftKey; 273 | ev.metaKey = !!e.metaKey; 274 | ev.button = 0; 275 | ev.relatedTarget = null; 276 | ev._constructed = true; 277 | target.dispatchEvent(ev); 278 | } 279 | }; 280 | 281 | return me; 282 | })(); 283 | function IScroll (el, options) { 284 | this.wrapper = typeof el == 'string' ? document.querySelector(el) : el; 285 | this.scroller = this.wrapper.children[0]; 286 | this.scrollerStyle = this.scroller.style; // cache style for better performance 287 | 288 | this.options = { 289 | 290 | resizeScrollbars: true, 291 | 292 | mouseWheelSpeed: 20, 293 | 294 | snapThreshold: 0.334, 295 | 296 | // INSERT POINT: OPTIONS 297 | disablePointer : !utils.hasPointer, 298 | disableTouch : utils.hasPointer || !utils.hasTouch, 299 | disableMouse : utils.hasPointer || utils.hasTouch, 300 | startX: 0, 301 | startY: 0, 302 | scrollY: true, 303 | directionLockThreshold: 5, 304 | momentum: true, 305 | 306 | bounce: true, 307 | bounceTime: 600, 308 | bounceEasing: '', 309 | 310 | preventDefault: true, 311 | preventDefaultException: { tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT)$/ }, 312 | 313 | HWCompositing: true, 314 | useTransition: true, 315 | useTransform: true, 316 | bindToWrapper: typeof window.onmousedown === "undefined" 317 | }; 318 | 319 | for ( var i in options ) { 320 | this.options[i] = options[i]; 321 | } 322 | 323 | // Normalize options 324 | this.translateZ = this.options.HWCompositing && utils.hasPerspective ? ' translateZ(0)' : ''; 325 | 326 | this.options.useTransition = utils.hasTransition && this.options.useTransition; 327 | this.options.useTransform = utils.hasTransform && this.options.useTransform; 328 | 329 | this.options.eventPassthrough = this.options.eventPassthrough === true ? 'vertical' : this.options.eventPassthrough; 330 | this.options.preventDefault = !this.options.eventPassthrough && this.options.preventDefault; 331 | 332 | // If you want eventPassthrough I have to lock one of the axes 333 | this.options.scrollY = this.options.eventPassthrough == 'vertical' ? false : this.options.scrollY; 334 | this.options.scrollX = this.options.eventPassthrough == 'horizontal' ? false : this.options.scrollX; 335 | 336 | // With eventPassthrough we also need lockDirection mechanism 337 | this.options.freeScroll = this.options.freeScroll && !this.options.eventPassthrough; 338 | this.options.directionLockThreshold = this.options.eventPassthrough ? 0 : this.options.directionLockThreshold; 339 | 340 | this.options.bounceEasing = typeof this.options.bounceEasing == 'string' ? utils.ease[this.options.bounceEasing] || utils.ease.circular : this.options.bounceEasing; 341 | 342 | this.options.resizePolling = this.options.resizePolling === undefined ? 60 : this.options.resizePolling; 343 | 344 | if ( this.options.tap === true ) { 345 | this.options.tap = 'tap'; 346 | } 347 | 348 | // https://github.com/cubiq/iscroll/issues/1029 349 | if (!this.options.useTransition && !this.options.useTransform) { 350 | if(!(/relative|absolute/i).test(this.scrollerStyle.position)) { 351 | this.scrollerStyle.position = "relative"; 352 | } 353 | } 354 | 355 | if ( this.options.shrinkScrollbars == 'scale' ) { 356 | this.options.useTransition = false; 357 | } 358 | 359 | this.options.invertWheelDirection = this.options.invertWheelDirection ? -1 : 1; 360 | 361 | // INSERT POINT: NORMALIZATION 362 | 363 | // Some defaults 364 | this.x = 0; 365 | this.y = 0; 366 | this.directionX = 0; 367 | this.directionY = 0; 368 | this._events = {}; 369 | 370 | // INSERT POINT: DEFAULTS 371 | 372 | this._init(); 373 | this.refresh(); 374 | 375 | this.scrollTo(this.options.startX, this.options.startY); 376 | this.enable(); 377 | } 378 | 379 | IScroll.prototype = { 380 | version: '5.2.0', 381 | 382 | _init: function () { 383 | this._initEvents(); 384 | 385 | if ( this.options.scrollbars || this.options.indicators ) { 386 | this._initIndicators(); 387 | } 388 | 389 | if ( this.options.mouseWheel ) { 390 | this._initWheel(); 391 | } 392 | 393 | if ( this.options.snap ) { 394 | this._initSnap(); 395 | } 396 | 397 | if ( this.options.keyBindings ) { 398 | this._initKeys(); 399 | } 400 | 401 | // INSERT POINT: _init 402 | 403 | }, 404 | 405 | destroy: function () { 406 | this._initEvents(true); 407 | clearTimeout(this.resizeTimeout); 408 | this.resizeTimeout = null; 409 | this._execEvent('destroy'); 410 | }, 411 | 412 | _transitionEnd: function (e) { 413 | if ( e.target != this.scroller || !this.isInTransition ) { 414 | return; 415 | } 416 | 417 | this._transitionTime(); 418 | if ( !this.resetPosition(this.options.bounceTime) ) { 419 | this.isInTransition = false; 420 | this._execEvent('scrollEnd'); 421 | } 422 | }, 423 | 424 | _start: function (e) { 425 | // React to left mouse button only 426 | if ( utils.eventType[e.type] != 1 ) { 427 | // for button property 428 | // http://unixpapa.com/js/mouse.html 429 | var button; 430 | if (!e.which) { 431 | /* IE case */ 432 | button = (e.button < 2) ? 0 : 433 | ((e.button == 4) ? 1 : 2); 434 | } else { 435 | /* All others */ 436 | button = e.button; 437 | } 438 | if ( button !== 0 ) { 439 | return; 440 | } 441 | } 442 | 443 | if ( !this.enabled || (this.initiated && utils.eventType[e.type] !== this.initiated) ) { 444 | return; 445 | } 446 | 447 | if ( this.options.preventDefault && !utils.isBadAndroid && !utils.preventDefaultException(e.target, this.options.preventDefaultException) ) { 448 | e.preventDefault(); 449 | } 450 | 451 | var point = e.touches ? e.touches[0] : e, 452 | pos; 453 | 454 | this.initiated = utils.eventType[e.type]; 455 | this.moved = false; 456 | this.distX = 0; 457 | this.distY = 0; 458 | this.directionX = 0; 459 | this.directionY = 0; 460 | this.directionLocked = 0; 461 | 462 | this.startTime = utils.getTime(); 463 | 464 | if ( this.options.useTransition && this.isInTransition ) { 465 | this._transitionTime(); 466 | this.isInTransition = false; 467 | pos = this.getComputedPosition(); 468 | this._translate(Math.round(pos.x), Math.round(pos.y)); 469 | this._execEvent('scrollEnd'); 470 | } else if ( !this.options.useTransition && this.isAnimating ) { 471 | this.isAnimating = false; 472 | this._execEvent('scrollEnd'); 473 | } 474 | 475 | this.startX = this.x; 476 | this.startY = this.y; 477 | this.absStartX = this.x; 478 | this.absStartY = this.y; 479 | this.pointX = point.pageX; 480 | this.pointY = point.pageY; 481 | 482 | this._execEvent('beforeScrollStart'); 483 | }, 484 | 485 | _move: function (e) { 486 | if ( !this.enabled || utils.eventType[e.type] !== this.initiated ) { 487 | return; 488 | } 489 | 490 | if ( this.options.preventDefault ) { // increases performance on Android? TODO: check! 491 | e.preventDefault(); 492 | } 493 | 494 | var point = e.touches ? e.touches[0] : e, 495 | deltaX = point.pageX - this.pointX, 496 | deltaY = point.pageY - this.pointY, 497 | timestamp = utils.getTime(), 498 | newX, newY, 499 | absDistX, absDistY; 500 | 501 | this.pointX = point.pageX; 502 | this.pointY = point.pageY; 503 | 504 | this.distX += deltaX; 505 | this.distY += deltaY; 506 | absDistX = Math.abs(this.distX); 507 | absDistY = Math.abs(this.distY); 508 | 509 | // We need to move at least 10 pixels for the scrolling to initiate 510 | if ( timestamp - this.endTime > 300 && (absDistX < 10 && absDistY < 10) ) { 511 | return; 512 | } 513 | 514 | // If you are scrolling in one direction lock the other 515 | if ( !this.directionLocked && !this.options.freeScroll ) { 516 | if ( absDistX > absDistY + this.options.directionLockThreshold ) { 517 | this.directionLocked = 'h'; // lock horizontally 518 | } else if ( absDistY >= absDistX + this.options.directionLockThreshold ) { 519 | this.directionLocked = 'v'; // lock vertically 520 | } else { 521 | this.directionLocked = 'n'; // no lock 522 | } 523 | } 524 | 525 | if ( this.directionLocked == 'h' ) { 526 | if ( this.options.eventPassthrough == 'vertical' ) { 527 | e.preventDefault(); 528 | } else if ( this.options.eventPassthrough == 'horizontal' ) { 529 | this.initiated = false; 530 | return; 531 | } 532 | 533 | deltaY = 0; 534 | } else if ( this.directionLocked == 'v' ) { 535 | if ( this.options.eventPassthrough == 'horizontal' ) { 536 | e.preventDefault(); 537 | } else if ( this.options.eventPassthrough == 'vertical' ) { 538 | this.initiated = false; 539 | return; 540 | } 541 | 542 | deltaX = 0; 543 | } 544 | 545 | deltaX = this.hasHorizontalScroll ? deltaX : 0; 546 | deltaY = this.hasVerticalScroll ? deltaY : 0; 547 | 548 | newX = this.x + deltaX; 549 | newY = this.y + deltaY; 550 | 551 | // Slow down if outside of the boundaries 552 | if ( newX > 0 || newX < this.maxScrollX ) { 553 | newX = this.options.bounce ? this.x + deltaX / 3 : newX > 0 ? 0 : this.maxScrollX; 554 | } 555 | if ( newY > 0 || newY < this.maxScrollY ) { 556 | newY = this.options.bounce ? this.y + deltaY / 3 : newY > 0 ? 0 : this.maxScrollY; 557 | } 558 | 559 | this.directionX = deltaX > 0 ? -1 : deltaX < 0 ? 1 : 0; 560 | this.directionY = deltaY > 0 ? -1 : deltaY < 0 ? 1 : 0; 561 | 562 | if ( !this.moved ) { 563 | this._execEvent('scrollStart'); 564 | } 565 | 566 | this.moved = true; 567 | 568 | this._translate(newX, newY); 569 | 570 | /* REPLACE START: _move */ 571 | 572 | if ( timestamp - this.startTime > 300 ) { 573 | this.startTime = timestamp; 574 | this.startX = this.x; 575 | this.startY = this.y; 576 | } 577 | 578 | /* REPLACE END: _move */ 579 | 580 | }, 581 | 582 | _end: function (e) { 583 | if ( !this.enabled || utils.eventType[e.type] !== this.initiated ) { 584 | return; 585 | } 586 | 587 | if ( this.options.preventDefault && !utils.preventDefaultException(e.target, this.options.preventDefaultException) ) { 588 | e.preventDefault(); 589 | } 590 | 591 | var point = e.changedTouches ? e.changedTouches[0] : e, 592 | momentumX, 593 | momentumY, 594 | duration = utils.getTime() - this.startTime, 595 | newX = Math.round(this.x), 596 | newY = Math.round(this.y), 597 | distanceX = Math.abs(newX - this.startX), 598 | distanceY = Math.abs(newY - this.startY), 599 | time = 0, 600 | easing = ''; 601 | 602 | this.isInTransition = 0; 603 | this.initiated = 0; 604 | this.endTime = utils.getTime(); 605 | 606 | // reset if we are outside of the boundaries 607 | if ( this.resetPosition(this.options.bounceTime) ) { 608 | return; 609 | } 610 | 611 | this.scrollTo(newX, newY); // ensures that the last position is rounded 612 | 613 | // we scrolled less than 10 pixels 614 | if ( !this.moved ) { 615 | if ( this.options.tap ) { 616 | utils.tap(e, this.options.tap); 617 | } 618 | 619 | if ( this.options.click ) { 620 | utils.click(e); 621 | } 622 | 623 | this._execEvent('scrollCancel'); 624 | return; 625 | } 626 | 627 | if ( this._events.flick && duration < 200 && distanceX < 100 && distanceY < 100 ) { 628 | this._execEvent('flick'); 629 | return; 630 | } 631 | 632 | // start momentum animation if needed 633 | if ( this.options.momentum && duration < 300 ) { 634 | momentumX = this.hasHorizontalScroll ? utils.momentum(this.x, this.startX, duration, this.maxScrollX, this.options.bounce ? this.wrapperWidth : 0, this.options.deceleration) : { destination: newX, duration: 0 }; 635 | momentumY = this.hasVerticalScroll ? utils.momentum(this.y, this.startY, duration, this.maxScrollY, this.options.bounce ? this.wrapperHeight : 0, this.options.deceleration) : { destination: newY, duration: 0 }; 636 | newX = momentumX.destination; 637 | newY = momentumY.destination; 638 | time = Math.max(momentumX.duration, momentumY.duration); 639 | this.isInTransition = 1; 640 | } 641 | 642 | 643 | if ( this.options.snap ) { 644 | var snap = this._nearestSnap(newX, newY); 645 | this.currentPage = snap; 646 | time = this.options.snapSpeed || Math.max( 647 | Math.max( 648 | Math.min(Math.abs(newX - snap.x), 1000), 649 | Math.min(Math.abs(newY - snap.y), 1000) 650 | ), 300); 651 | newX = snap.x; 652 | newY = snap.y; 653 | 654 | this.directionX = 0; 655 | this.directionY = 0; 656 | easing = this.options.bounceEasing; 657 | } 658 | 659 | // INSERT POINT: _end 660 | 661 | if ( newX != this.x || newY != this.y ) { 662 | // change easing function when scroller goes out of the boundaries 663 | if ( newX > 0 || newX < this.maxScrollX || newY > 0 || newY < this.maxScrollY ) { 664 | easing = utils.ease.quadratic; 665 | } 666 | 667 | this.scrollTo(newX, newY, time, easing); 668 | return; 669 | } 670 | 671 | this._execEvent('scrollEnd'); 672 | }, 673 | 674 | _resize: function () { 675 | var that = this; 676 | 677 | clearTimeout(this.resizeTimeout); 678 | 679 | this.resizeTimeout = setTimeout(function () { 680 | that.refresh(); 681 | }, this.options.resizePolling); 682 | }, 683 | 684 | resetPosition: function (time) { 685 | var x = this.x, 686 | y = this.y; 687 | 688 | time = time || 0; 689 | 690 | if ( !this.hasHorizontalScroll || this.x > 0 ) { 691 | x = 0; 692 | } else if ( this.x < this.maxScrollX ) { 693 | x = this.maxScrollX; 694 | } 695 | 696 | if ( !this.hasVerticalScroll || this.y > 0 ) { 697 | y = 0; 698 | } else if ( this.y < this.maxScrollY ) { 699 | y = this.maxScrollY; 700 | } 701 | 702 | if ( x == this.x && y == this.y ) { 703 | return false; 704 | } 705 | 706 | this.scrollTo(x, y, time, this.options.bounceEasing); 707 | 708 | return true; 709 | }, 710 | 711 | disable: function () { 712 | this.enabled = false; 713 | }, 714 | 715 | enable: function () { 716 | this.enabled = true; 717 | }, 718 | 719 | refresh: function () { 720 | var rf = this.wrapper.offsetHeight; // Force reflow 721 | 722 | this.wrapperWidth = this.wrapper.clientWidth; 723 | this.wrapperHeight = this.wrapper.clientHeight; 724 | 725 | /* REPLACE START: refresh */ 726 | 727 | this.scrollerWidth = this.scroller.offsetWidth; 728 | this.scrollerHeight = this.scroller.offsetHeight; 729 | 730 | this.maxScrollX = this.wrapperWidth - this.scrollerWidth; 731 | this.maxScrollY = this.wrapperHeight - this.scrollerHeight; 732 | 733 | /* REPLACE END: refresh */ 734 | 735 | this.hasHorizontalScroll = this.options.scrollX && this.maxScrollX < 0; 736 | this.hasVerticalScroll = this.options.scrollY && this.maxScrollY < 0; 737 | 738 | if ( !this.hasHorizontalScroll ) { 739 | this.maxScrollX = 0; 740 | this.scrollerWidth = this.wrapperWidth; 741 | } 742 | 743 | if ( !this.hasVerticalScroll ) { 744 | this.maxScrollY = 0; 745 | this.scrollerHeight = this.wrapperHeight; 746 | } 747 | 748 | this.endTime = 0; 749 | this.directionX = 0; 750 | this.directionY = 0; 751 | 752 | this.wrapperOffset = utils.offset(this.wrapper); 753 | 754 | this._execEvent('refresh'); 755 | 756 | this.resetPosition(); 757 | 758 | // INSERT POINT: _refresh 759 | 760 | }, 761 | 762 | on: function (type, fn) { 763 | if ( !this._events[type] ) { 764 | this._events[type] = []; 765 | } 766 | 767 | this._events[type].push(fn); 768 | }, 769 | 770 | off: function (type, fn) { 771 | if ( !this._events[type] ) { 772 | return; 773 | } 774 | 775 | var index = this._events[type].indexOf(fn); 776 | 777 | if ( index > -1 ) { 778 | this._events[type].splice(index, 1); 779 | } 780 | }, 781 | 782 | _execEvent: function (type) { 783 | if ( !this._events[type] ) { 784 | return; 785 | } 786 | 787 | var i = 0, 788 | l = this._events[type].length; 789 | 790 | if ( !l ) { 791 | return; 792 | } 793 | 794 | for ( ; i < l; i++ ) { 795 | this._events[type][i].apply(this, [].slice.call(arguments, 1)); 796 | } 797 | }, 798 | 799 | scrollBy: function (x, y, time, easing) { 800 | x = this.x + x; 801 | y = this.y + y; 802 | time = time || 0; 803 | 804 | this.scrollTo(x, y, time, easing); 805 | }, 806 | 807 | scrollTo: function (x, y, time, easing) { 808 | easing = easing || utils.ease.circular; 809 | 810 | this.isInTransition = this.options.useTransition && time > 0; 811 | var transitionType = this.options.useTransition && easing.style; 812 | if ( !time || transitionType ) { 813 | if(transitionType) { 814 | this._transitionTimingFunction(easing.style); 815 | this._transitionTime(time); 816 | } 817 | this._translate(x, y); 818 | } else { 819 | this._animate(x, y, time, easing.fn); 820 | } 821 | }, 822 | 823 | scrollToElement: function (el, time, offsetX, offsetY, easing) { 824 | el = el.nodeType ? el : this.scroller.querySelector(el); 825 | 826 | if ( !el ) { 827 | return; 828 | } 829 | 830 | var pos = utils.offset(el); 831 | 832 | pos.left -= this.wrapperOffset.left; 833 | pos.top -= this.wrapperOffset.top; 834 | 835 | // if offsetX/Y are true we center the element to the screen 836 | if ( offsetX === true ) { 837 | offsetX = Math.round(el.offsetWidth / 2 - this.wrapper.offsetWidth / 2); 838 | } 839 | if ( offsetY === true ) { 840 | offsetY = Math.round(el.offsetHeight / 2 - this.wrapper.offsetHeight / 2); 841 | } 842 | 843 | pos.left -= offsetX || 0; 844 | pos.top -= offsetY || 0; 845 | 846 | pos.left = pos.left > 0 ? 0 : pos.left < this.maxScrollX ? this.maxScrollX : pos.left; 847 | pos.top = pos.top > 0 ? 0 : pos.top < this.maxScrollY ? this.maxScrollY : pos.top; 848 | 849 | time = time === undefined || time === null || time === 'auto' ? Math.max(Math.abs(this.x-pos.left), Math.abs(this.y-pos.top)) : time; 850 | 851 | this.scrollTo(pos.left, pos.top, time, easing); 852 | }, 853 | 854 | _transitionTime: function (time) { 855 | if (!this.options.useTransition) { 856 | return; 857 | } 858 | time = time || 0; 859 | var durationProp = utils.style.transitionDuration; 860 | if(!durationProp) { 861 | return; 862 | } 863 | 864 | this.scrollerStyle[durationProp] = time + 'ms'; 865 | 866 | if ( !time && utils.isBadAndroid ) { 867 | this.scrollerStyle[durationProp] = '0.0001ms'; 868 | // remove 0.0001ms 869 | var self = this; 870 | rAF(function() { 871 | if(self.scrollerStyle[durationProp] === '0.0001ms') { 872 | self.scrollerStyle[durationProp] = '0s'; 873 | } 874 | }); 875 | } 876 | 877 | 878 | if ( this.indicators ) { 879 | for ( var i = this.indicators.length; i--; ) { 880 | this.indicators[i].transitionTime(time); 881 | } 882 | } 883 | 884 | 885 | // INSERT POINT: _transitionTime 886 | 887 | }, 888 | 889 | _transitionTimingFunction: function (easing) { 890 | this.scrollerStyle[utils.style.transitionTimingFunction] = easing; 891 | 892 | 893 | if ( this.indicators ) { 894 | for ( var i = this.indicators.length; i--; ) { 895 | this.indicators[i].transitionTimingFunction(easing); 896 | } 897 | } 898 | 899 | 900 | // INSERT POINT: _transitionTimingFunction 901 | 902 | }, 903 | 904 | _translate: function (x, y) { 905 | if ( this.options.useTransform ) { 906 | 907 | /* REPLACE START: _translate */ 908 | 909 | this.scrollerStyle[utils.style.transform] = 'translate(' + x + 'px,' + y + 'px)' + this.translateZ; 910 | 911 | /* REPLACE END: _translate */ 912 | 913 | } else { 914 | x = Math.round(x); 915 | y = Math.round(y); 916 | this.scrollerStyle.left = x + 'px'; 917 | this.scrollerStyle.top = y + 'px'; 918 | } 919 | 920 | this.x = x; 921 | this.y = y; 922 | 923 | 924 | if ( this.indicators ) { 925 | for ( var i = this.indicators.length; i--; ) { 926 | this.indicators[i].updatePosition(); 927 | } 928 | } 929 | 930 | 931 | // INSERT POINT: _translate 932 | 933 | }, 934 | 935 | _initEvents: function (remove) { 936 | var eventType = remove ? utils.removeEvent : utils.addEvent, 937 | target = this.options.bindToWrapper ? this.wrapper : window; 938 | 939 | eventType(window, 'orientationchange', this); 940 | eventType(window, 'resize', this); 941 | 942 | if ( this.options.click ) { 943 | eventType(this.wrapper, 'click', this, true); 944 | } 945 | 946 | if ( !this.options.disableMouse ) { 947 | eventType(this.wrapper, 'mousedown', this); 948 | eventType(target, 'mousemove', this); 949 | eventType(target, 'mousecancel', this); 950 | eventType(target, 'mouseup', this); 951 | } 952 | 953 | if ( utils.hasPointer && !this.options.disablePointer ) { 954 | eventType(this.wrapper, utils.prefixPointerEvent('pointerdown'), this); 955 | eventType(target, utils.prefixPointerEvent('pointermove'), this); 956 | eventType(target, utils.prefixPointerEvent('pointercancel'), this); 957 | eventType(target, utils.prefixPointerEvent('pointerup'), this); 958 | } 959 | 960 | if ( utils.hasTouch && !this.options.disableTouch ) { 961 | eventType(this.wrapper, 'touchstart', this); 962 | eventType(target, 'touchmove', this); 963 | eventType(target, 'touchcancel', this); 964 | eventType(target, 'touchend', this); 965 | } 966 | 967 | eventType(this.scroller, 'transitionend', this); 968 | eventType(this.scroller, 'webkitTransitionEnd', this); 969 | eventType(this.scroller, 'oTransitionEnd', this); 970 | eventType(this.scroller, 'MSTransitionEnd', this); 971 | }, 972 | 973 | getComputedPosition: function () { 974 | var matrix = window.getComputedStyle(this.scroller, null), 975 | x, y; 976 | 977 | if ( this.options.useTransform ) { 978 | matrix = matrix[utils.style.transform].split(')')[0].split(', '); 979 | x = +(matrix[12] || matrix[4]); 980 | y = +(matrix[13] || matrix[5]); 981 | } else { 982 | x = +matrix.left.replace(/[^-\d.]/g, ''); 983 | y = +matrix.top.replace(/[^-\d.]/g, ''); 984 | } 985 | 986 | return { x: x, y: y }; 987 | }, 988 | _initIndicators: function () { 989 | var interactive = this.options.interactiveScrollbars, 990 | customStyle = typeof this.options.scrollbars != 'string', 991 | indicators = [], 992 | indicator; 993 | 994 | var that = this; 995 | 996 | this.indicators = []; 997 | 998 | if ( this.options.scrollbars ) { 999 | // Vertical scrollbar 1000 | if ( this.options.scrollY ) { 1001 | indicator = { 1002 | el: createDefaultScrollbar('v', interactive, this.options.scrollbars), 1003 | interactive: interactive, 1004 | defaultScrollbars: true, 1005 | customStyle: customStyle, 1006 | resize: this.options.resizeScrollbars, 1007 | shrink: this.options.shrinkScrollbars, 1008 | fade: this.options.fadeScrollbars, 1009 | listenX: false 1010 | }; 1011 | 1012 | this.wrapper.appendChild(indicator.el); 1013 | indicators.push(indicator); 1014 | } 1015 | 1016 | // Horizontal scrollbar 1017 | if ( this.options.scrollX ) { 1018 | indicator = { 1019 | el: createDefaultScrollbar('h', interactive, this.options.scrollbars), 1020 | interactive: interactive, 1021 | defaultScrollbars: true, 1022 | customStyle: customStyle, 1023 | resize: this.options.resizeScrollbars, 1024 | shrink: this.options.shrinkScrollbars, 1025 | fade: this.options.fadeScrollbars, 1026 | listenY: false 1027 | }; 1028 | 1029 | this.wrapper.appendChild(indicator.el); 1030 | indicators.push(indicator); 1031 | } 1032 | } 1033 | 1034 | if ( this.options.indicators ) { 1035 | // TODO: check concat compatibility 1036 | indicators = indicators.concat(this.options.indicators); 1037 | } 1038 | 1039 | for ( var i = indicators.length; i--; ) { 1040 | this.indicators.push( new Indicator(this, indicators[i]) ); 1041 | } 1042 | 1043 | // TODO: check if we can use array.map (wide compatibility and performance issues) 1044 | function _indicatorsMap (fn) { 1045 | if (that.indicators) { 1046 | for ( var i = that.indicators.length; i--; ) { 1047 | fn.call(that.indicators[i]); 1048 | } 1049 | } 1050 | } 1051 | 1052 | if ( this.options.fadeScrollbars ) { 1053 | this.on('scrollEnd', function () { 1054 | _indicatorsMap(function () { 1055 | this.fade(); 1056 | }); 1057 | }); 1058 | 1059 | this.on('scrollCancel', function () { 1060 | _indicatorsMap(function () { 1061 | this.fade(); 1062 | }); 1063 | }); 1064 | 1065 | this.on('scrollStart', function () { 1066 | _indicatorsMap(function () { 1067 | this.fade(1); 1068 | }); 1069 | }); 1070 | 1071 | this.on('beforeScrollStart', function () { 1072 | _indicatorsMap(function () { 1073 | this.fade(1, true); 1074 | }); 1075 | }); 1076 | } 1077 | 1078 | 1079 | this.on('refresh', function () { 1080 | _indicatorsMap(function () { 1081 | this.refresh(); 1082 | }); 1083 | }); 1084 | 1085 | this.on('destroy', function () { 1086 | _indicatorsMap(function () { 1087 | this.destroy(); 1088 | }); 1089 | 1090 | delete this.indicators; 1091 | }); 1092 | }, 1093 | 1094 | _initWheel: function () { 1095 | utils.addEvent(this.wrapper, 'wheel', this); 1096 | utils.addEvent(this.wrapper, 'mousewheel', this); 1097 | utils.addEvent(this.wrapper, 'DOMMouseScroll', this); 1098 | 1099 | this.on('destroy', function () { 1100 | clearTimeout(this.wheelTimeout); 1101 | this.wheelTimeout = null; 1102 | utils.removeEvent(this.wrapper, 'wheel', this); 1103 | utils.removeEvent(this.wrapper, 'mousewheel', this); 1104 | utils.removeEvent(this.wrapper, 'DOMMouseScroll', this); 1105 | }); 1106 | }, 1107 | 1108 | _wheel: function (e) { 1109 | if ( !this.enabled ) { 1110 | return; 1111 | } 1112 | 1113 | e.preventDefault(); 1114 | 1115 | var wheelDeltaX, wheelDeltaY, 1116 | newX, newY, 1117 | that = this; 1118 | 1119 | if ( this.wheelTimeout === undefined ) { 1120 | that._execEvent('scrollStart'); 1121 | } 1122 | 1123 | // Execute the scrollEnd event after 400ms the wheel stopped scrolling 1124 | clearTimeout(this.wheelTimeout); 1125 | this.wheelTimeout = setTimeout(function () { 1126 | if(!that.options.snap) { 1127 | that._execEvent('scrollEnd'); 1128 | } 1129 | that.wheelTimeout = undefined; 1130 | }, 400); 1131 | 1132 | if ( 'deltaX' in e ) { 1133 | if (e.deltaMode === 1) { 1134 | wheelDeltaX = -e.deltaX * this.options.mouseWheelSpeed; 1135 | wheelDeltaY = -e.deltaY * this.options.mouseWheelSpeed; 1136 | } else { 1137 | wheelDeltaX = -e.deltaX; 1138 | wheelDeltaY = -e.deltaY; 1139 | } 1140 | } else if ( 'wheelDeltaX' in e ) { 1141 | wheelDeltaX = e.wheelDeltaX / 120 * this.options.mouseWheelSpeed; 1142 | wheelDeltaY = e.wheelDeltaY / 120 * this.options.mouseWheelSpeed; 1143 | } else if ( 'wheelDelta' in e ) { 1144 | wheelDeltaX = wheelDeltaY = e.wheelDelta / 120 * this.options.mouseWheelSpeed; 1145 | } else if ( 'detail' in e ) { 1146 | wheelDeltaX = wheelDeltaY = -e.detail / 3 * this.options.mouseWheelSpeed; 1147 | } else { 1148 | return; 1149 | } 1150 | 1151 | wheelDeltaX *= this.options.invertWheelDirection; 1152 | wheelDeltaY *= this.options.invertWheelDirection; 1153 | 1154 | if ( !this.hasVerticalScroll ) { 1155 | wheelDeltaX = wheelDeltaY; 1156 | wheelDeltaY = 0; 1157 | } 1158 | 1159 | if ( this.options.snap ) { 1160 | newX = this.currentPage.pageX; 1161 | newY = this.currentPage.pageY; 1162 | 1163 | if ( wheelDeltaX > 0 ) { 1164 | newX--; 1165 | } else if ( wheelDeltaX < 0 ) { 1166 | newX++; 1167 | } 1168 | 1169 | if ( wheelDeltaY > 0 ) { 1170 | newY--; 1171 | } else if ( wheelDeltaY < 0 ) { 1172 | newY++; 1173 | } 1174 | 1175 | this.goToPage(newX, newY); 1176 | 1177 | return; 1178 | } 1179 | 1180 | newX = this.x + Math.round(this.hasHorizontalScroll ? wheelDeltaX : 0); 1181 | newY = this.y + Math.round(this.hasVerticalScroll ? wheelDeltaY : 0); 1182 | 1183 | this.directionX = wheelDeltaX > 0 ? -1 : wheelDeltaX < 0 ? 1 : 0; 1184 | this.directionY = wheelDeltaY > 0 ? -1 : wheelDeltaY < 0 ? 1 : 0; 1185 | 1186 | if ( newX > 0 ) { 1187 | newX = 0; 1188 | } else if ( newX < this.maxScrollX ) { 1189 | newX = this.maxScrollX; 1190 | } 1191 | 1192 | if ( newY > 0 ) { 1193 | newY = 0; 1194 | } else if ( newY < this.maxScrollY ) { 1195 | newY = this.maxScrollY; 1196 | } 1197 | 1198 | this.scrollTo(newX, newY, 0); 1199 | 1200 | // INSERT POINT: _wheel 1201 | }, 1202 | 1203 | _initSnap: function () { 1204 | this.currentPage = {}; 1205 | 1206 | if ( typeof this.options.snap == 'string' ) { 1207 | this.options.snap = this.scroller.querySelectorAll(this.options.snap); 1208 | } 1209 | 1210 | this.on('refresh', function () { 1211 | var i = 0, l, 1212 | m = 0, n, 1213 | cx, cy, 1214 | x = 0, y, 1215 | stepX = this.options.snapStepX || this.wrapperWidth, 1216 | stepY = this.options.snapStepY || this.wrapperHeight, 1217 | el; 1218 | 1219 | this.pages = []; 1220 | 1221 | if ( !this.wrapperWidth || !this.wrapperHeight || !this.scrollerWidth || !this.scrollerHeight ) { 1222 | return; 1223 | } 1224 | 1225 | if ( this.options.snap === true ) { 1226 | cx = Math.round( stepX / 2 ); 1227 | cy = Math.round( stepY / 2 ); 1228 | 1229 | while ( x > -this.scrollerWidth ) { 1230 | this.pages[i] = []; 1231 | l = 0; 1232 | y = 0; 1233 | 1234 | while ( y > -this.scrollerHeight ) { 1235 | this.pages[i][l] = { 1236 | x: Math.max(x, this.maxScrollX), 1237 | y: Math.max(y, this.maxScrollY), 1238 | width: stepX, 1239 | height: stepY, 1240 | cx: x - cx, 1241 | cy: y - cy 1242 | }; 1243 | 1244 | y -= stepY; 1245 | l++; 1246 | } 1247 | 1248 | x -= stepX; 1249 | i++; 1250 | } 1251 | } else { 1252 | el = this.options.snap; 1253 | l = el.length; 1254 | n = -1; 1255 | 1256 | for ( ; i < l; i++ ) { 1257 | if ( i === 0 || el[i].offsetLeft <= el[i-1].offsetLeft ) { 1258 | m = 0; 1259 | n++; 1260 | } 1261 | 1262 | if ( !this.pages[m] ) { 1263 | this.pages[m] = []; 1264 | } 1265 | 1266 | x = Math.max(-el[i].offsetLeft, this.maxScrollX); 1267 | y = Math.max(-el[i].offsetTop, this.maxScrollY); 1268 | cx = x - Math.round(el[i].offsetWidth / 2); 1269 | cy = y - Math.round(el[i].offsetHeight / 2); 1270 | 1271 | this.pages[m][n] = { 1272 | x: x, 1273 | y: y, 1274 | width: el[i].offsetWidth, 1275 | height: el[i].offsetHeight, 1276 | cx: cx, 1277 | cy: cy 1278 | }; 1279 | 1280 | if ( x > this.maxScrollX ) { 1281 | m++; 1282 | } 1283 | } 1284 | } 1285 | 1286 | this.goToPage(this.currentPage.pageX || 0, this.currentPage.pageY || 0, 0); 1287 | 1288 | // Update snap threshold if needed 1289 | if ( this.options.snapThreshold % 1 === 0 ) { 1290 | this.snapThresholdX = this.options.snapThreshold; 1291 | this.snapThresholdY = this.options.snapThreshold; 1292 | } else { 1293 | this.snapThresholdX = Math.round(this.pages[this.currentPage.pageX][this.currentPage.pageY].width * this.options.snapThreshold); 1294 | this.snapThresholdY = Math.round(this.pages[this.currentPage.pageX][this.currentPage.pageY].height * this.options.snapThreshold); 1295 | } 1296 | }); 1297 | 1298 | this.on('flick', function () { 1299 | var time = this.options.snapSpeed || Math.max( 1300 | Math.max( 1301 | Math.min(Math.abs(this.x - this.startX), 1000), 1302 | Math.min(Math.abs(this.y - this.startY), 1000) 1303 | ), 300); 1304 | 1305 | this.goToPage( 1306 | this.currentPage.pageX + this.directionX, 1307 | this.currentPage.pageY + this.directionY, 1308 | time 1309 | ); 1310 | }); 1311 | }, 1312 | 1313 | _nearestSnap: function (x, y) { 1314 | if ( !this.pages.length ) { 1315 | return { x: 0, y: 0, pageX: 0, pageY: 0 }; 1316 | } 1317 | 1318 | var i = 0, 1319 | l = this.pages.length, 1320 | m = 0; 1321 | 1322 | // Check if we exceeded the snap threshold 1323 | if ( Math.abs(x - this.absStartX) < this.snapThresholdX && 1324 | Math.abs(y - this.absStartY) < this.snapThresholdY ) { 1325 | return this.currentPage; 1326 | } 1327 | 1328 | if ( x > 0 ) { 1329 | x = 0; 1330 | } else if ( x < this.maxScrollX ) { 1331 | x = this.maxScrollX; 1332 | } 1333 | 1334 | if ( y > 0 ) { 1335 | y = 0; 1336 | } else if ( y < this.maxScrollY ) { 1337 | y = this.maxScrollY; 1338 | } 1339 | 1340 | for ( ; i < l; i++ ) { 1341 | if ( x >= this.pages[i][0].cx ) { 1342 | x = this.pages[i][0].x; 1343 | break; 1344 | } 1345 | } 1346 | 1347 | l = this.pages[i].length; 1348 | 1349 | for ( ; m < l; m++ ) { 1350 | if ( y >= this.pages[0][m].cy ) { 1351 | y = this.pages[0][m].y; 1352 | break; 1353 | } 1354 | } 1355 | 1356 | if ( i == this.currentPage.pageX ) { 1357 | i += this.directionX; 1358 | 1359 | if ( i < 0 ) { 1360 | i = 0; 1361 | } else if ( i >= this.pages.length ) { 1362 | i = this.pages.length - 1; 1363 | } 1364 | 1365 | x = this.pages[i][0].x; 1366 | } 1367 | 1368 | if ( m == this.currentPage.pageY ) { 1369 | m += this.directionY; 1370 | 1371 | if ( m < 0 ) { 1372 | m = 0; 1373 | } else if ( m >= this.pages[0].length ) { 1374 | m = this.pages[0].length - 1; 1375 | } 1376 | 1377 | y = this.pages[0][m].y; 1378 | } 1379 | 1380 | return { 1381 | x: x, 1382 | y: y, 1383 | pageX: i, 1384 | pageY: m 1385 | }; 1386 | }, 1387 | 1388 | goToPage: function (x, y, time, easing) { 1389 | easing = easing || this.options.bounceEasing; 1390 | 1391 | if ( x >= this.pages.length ) { 1392 | x = this.pages.length - 1; 1393 | } else if ( x < 0 ) { 1394 | x = 0; 1395 | } 1396 | 1397 | if ( y >= this.pages[x].length ) { 1398 | y = this.pages[x].length - 1; 1399 | } else if ( y < 0 ) { 1400 | y = 0; 1401 | } 1402 | 1403 | var posX = this.pages[x][y].x, 1404 | posY = this.pages[x][y].y; 1405 | 1406 | time = time === undefined ? this.options.snapSpeed || Math.max( 1407 | Math.max( 1408 | Math.min(Math.abs(posX - this.x), 1000), 1409 | Math.min(Math.abs(posY - this.y), 1000) 1410 | ), 300) : time; 1411 | 1412 | this.currentPage = { 1413 | x: posX, 1414 | y: posY, 1415 | pageX: x, 1416 | pageY: y 1417 | }; 1418 | 1419 | this.scrollTo(posX, posY, time, easing); 1420 | }, 1421 | 1422 | next: function (time, easing) { 1423 | var x = this.currentPage.pageX, 1424 | y = this.currentPage.pageY; 1425 | 1426 | x++; 1427 | 1428 | if ( x >= this.pages.length && this.hasVerticalScroll ) { 1429 | x = 0; 1430 | y++; 1431 | } 1432 | 1433 | this.goToPage(x, y, time, easing); 1434 | }, 1435 | 1436 | prev: function (time, easing) { 1437 | var x = this.currentPage.pageX, 1438 | y = this.currentPage.pageY; 1439 | 1440 | x--; 1441 | 1442 | if ( x < 0 && this.hasVerticalScroll ) { 1443 | x = 0; 1444 | y--; 1445 | } 1446 | 1447 | this.goToPage(x, y, time, easing); 1448 | }, 1449 | 1450 | _initKeys: function (e) { 1451 | // default key bindings 1452 | var keys = { 1453 | pageUp: 33, 1454 | pageDown: 34, 1455 | end: 35, 1456 | home: 36, 1457 | left: 37, 1458 | up: 38, 1459 | right: 39, 1460 | down: 40 1461 | }; 1462 | var i; 1463 | 1464 | // if you give me characters I give you keycode 1465 | if ( typeof this.options.keyBindings == 'object' ) { 1466 | for ( i in this.options.keyBindings ) { 1467 | if ( typeof this.options.keyBindings[i] == 'string' ) { 1468 | this.options.keyBindings[i] = this.options.keyBindings[i].toUpperCase().charCodeAt(0); 1469 | } 1470 | } 1471 | } else { 1472 | this.options.keyBindings = {}; 1473 | } 1474 | 1475 | for ( i in keys ) { 1476 | this.options.keyBindings[i] = this.options.keyBindings[i] || keys[i]; 1477 | } 1478 | 1479 | utils.addEvent(window, 'keydown', this); 1480 | 1481 | this.on('destroy', function () { 1482 | utils.removeEvent(window, 'keydown', this); 1483 | }); 1484 | }, 1485 | 1486 | _key: function (e) { 1487 | if ( !this.enabled ) { 1488 | return; 1489 | } 1490 | 1491 | var snap = this.options.snap, // we are using this alot, better to cache it 1492 | newX = snap ? this.currentPage.pageX : this.x, 1493 | newY = snap ? this.currentPage.pageY : this.y, 1494 | now = utils.getTime(), 1495 | prevTime = this.keyTime || 0, 1496 | acceleration = 0.250, 1497 | pos; 1498 | 1499 | if ( this.options.useTransition && this.isInTransition ) { 1500 | pos = this.getComputedPosition(); 1501 | 1502 | this._translate(Math.round(pos.x), Math.round(pos.y)); 1503 | this.isInTransition = false; 1504 | } 1505 | 1506 | this.keyAcceleration = now - prevTime < 200 ? Math.min(this.keyAcceleration + acceleration, 50) : 0; 1507 | 1508 | switch ( e.keyCode ) { 1509 | case this.options.keyBindings.pageUp: 1510 | if ( this.hasHorizontalScroll && !this.hasVerticalScroll ) { 1511 | newX += snap ? 1 : this.wrapperWidth; 1512 | } else { 1513 | newY += snap ? 1 : this.wrapperHeight; 1514 | } 1515 | break; 1516 | case this.options.keyBindings.pageDown: 1517 | if ( this.hasHorizontalScroll && !this.hasVerticalScroll ) { 1518 | newX -= snap ? 1 : this.wrapperWidth; 1519 | } else { 1520 | newY -= snap ? 1 : this.wrapperHeight; 1521 | } 1522 | break; 1523 | case this.options.keyBindings.end: 1524 | newX = snap ? this.pages.length-1 : this.maxScrollX; 1525 | newY = snap ? this.pages[0].length-1 : this.maxScrollY; 1526 | break; 1527 | case this.options.keyBindings.home: 1528 | newX = 0; 1529 | newY = 0; 1530 | break; 1531 | case this.options.keyBindings.left: 1532 | newX += snap ? -1 : 5 + this.keyAcceleration>>0; 1533 | break; 1534 | case this.options.keyBindings.up: 1535 | newY += snap ? 1 : 5 + this.keyAcceleration>>0; 1536 | break; 1537 | case this.options.keyBindings.right: 1538 | newX -= snap ? -1 : 5 + this.keyAcceleration>>0; 1539 | break; 1540 | case this.options.keyBindings.down: 1541 | newY -= snap ? 1 : 5 + this.keyAcceleration>>0; 1542 | break; 1543 | default: 1544 | return; 1545 | } 1546 | 1547 | if ( snap ) { 1548 | this.goToPage(newX, newY); 1549 | return; 1550 | } 1551 | 1552 | if ( newX > 0 ) { 1553 | newX = 0; 1554 | this.keyAcceleration = 0; 1555 | } else if ( newX < this.maxScrollX ) { 1556 | newX = this.maxScrollX; 1557 | this.keyAcceleration = 0; 1558 | } 1559 | 1560 | if ( newY > 0 ) { 1561 | newY = 0; 1562 | this.keyAcceleration = 0; 1563 | } else if ( newY < this.maxScrollY ) { 1564 | newY = this.maxScrollY; 1565 | this.keyAcceleration = 0; 1566 | } 1567 | 1568 | this.scrollTo(newX, newY, 0); 1569 | 1570 | this.keyTime = now; 1571 | }, 1572 | 1573 | _animate: function (destX, destY, duration, easingFn) { 1574 | var that = this, 1575 | startX = this.x, 1576 | startY = this.y, 1577 | startTime = utils.getTime(), 1578 | destTime = startTime + duration; 1579 | 1580 | function step () { 1581 | var now = utils.getTime(), 1582 | newX, newY, 1583 | easing; 1584 | 1585 | if ( now >= destTime ) { 1586 | that.isAnimating = false; 1587 | that._translate(destX, destY); 1588 | 1589 | if ( !that.resetPosition(that.options.bounceTime) ) { 1590 | that._execEvent('scrollEnd'); 1591 | } 1592 | 1593 | return; 1594 | } 1595 | 1596 | now = ( now - startTime ) / duration; 1597 | easing = easingFn(now); 1598 | newX = ( destX - startX ) * easing + startX; 1599 | newY = ( destY - startY ) * easing + startY; 1600 | that._translate(newX, newY); 1601 | 1602 | if ( that.isAnimating ) { 1603 | rAF(step); 1604 | } 1605 | } 1606 | 1607 | this.isAnimating = true; 1608 | step(); 1609 | }, 1610 | handleEvent: function (e) { 1611 | switch ( e.type ) { 1612 | case 'touchstart': 1613 | case 'pointerdown': 1614 | case 'MSPointerDown': 1615 | case 'mousedown': 1616 | this._start(e); 1617 | break; 1618 | case 'touchmove': 1619 | case 'pointermove': 1620 | case 'MSPointerMove': 1621 | case 'mousemove': 1622 | this._move(e); 1623 | break; 1624 | case 'touchend': 1625 | case 'pointerup': 1626 | case 'MSPointerUp': 1627 | case 'mouseup': 1628 | case 'touchcancel': 1629 | case 'pointercancel': 1630 | case 'MSPointerCancel': 1631 | case 'mousecancel': 1632 | this._end(e); 1633 | break; 1634 | case 'orientationchange': 1635 | case 'resize': 1636 | this._resize(); 1637 | break; 1638 | case 'transitionend': 1639 | case 'webkitTransitionEnd': 1640 | case 'oTransitionEnd': 1641 | case 'MSTransitionEnd': 1642 | this._transitionEnd(e); 1643 | break; 1644 | case 'wheel': 1645 | case 'DOMMouseScroll': 1646 | case 'mousewheel': 1647 | this._wheel(e); 1648 | break; 1649 | case 'keydown': 1650 | this._key(e); 1651 | break; 1652 | case 'click': 1653 | if ( this.enabled && !e._constructed ) { 1654 | e.preventDefault(); 1655 | e.stopPropagation(); 1656 | } 1657 | break; 1658 | } 1659 | } 1660 | }; 1661 | function createDefaultScrollbar (direction, interactive, type) { 1662 | var scrollbar = document.createElement('div'), 1663 | indicator = document.createElement('div'); 1664 | 1665 | if ( type === true ) { 1666 | scrollbar.style.cssText = 'position:absolute;z-index:9999'; 1667 | indicator.style.cssText = '-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;position:absolute;background:rgba(0,0,0,0.5);border:1px solid rgba(255,255,255,0.9);border-radius:3px'; 1668 | } 1669 | 1670 | indicator.className = 'iScrollIndicator'; 1671 | 1672 | if ( direction == 'h' ) { 1673 | if ( type === true ) { 1674 | scrollbar.style.cssText += ';height:7px;left:2px;right:2px;bottom:0'; 1675 | indicator.style.height = '100%'; 1676 | } 1677 | scrollbar.className = 'iScrollHorizontalScrollbar'; 1678 | } else { 1679 | if ( type === true ) { 1680 | scrollbar.style.cssText += ';width:7px;bottom:2px;top:2px;right:1px'; 1681 | indicator.style.width = '100%'; 1682 | } 1683 | scrollbar.className = 'iScrollVerticalScrollbar'; 1684 | } 1685 | 1686 | scrollbar.style.cssText += ';overflow:hidden'; 1687 | 1688 | if ( !interactive ) { 1689 | scrollbar.style.pointerEvents = 'none'; 1690 | } 1691 | 1692 | scrollbar.appendChild(indicator); 1693 | 1694 | return scrollbar; 1695 | } 1696 | 1697 | function Indicator (scroller, options) { 1698 | this.wrapper = typeof options.el == 'string' ? document.querySelector(options.el) : options.el; 1699 | this.wrapperStyle = this.wrapper.style; 1700 | this.indicator = this.wrapper.children[0]; 1701 | this.indicatorStyle = this.indicator.style; 1702 | this.scroller = scroller; 1703 | 1704 | this.options = { 1705 | listenX: true, 1706 | listenY: true, 1707 | interactive: false, 1708 | resize: true, 1709 | defaultScrollbars: false, 1710 | shrink: false, 1711 | fade: false, 1712 | speedRatioX: 0, 1713 | speedRatioY: 0 1714 | }; 1715 | 1716 | for ( var i in options ) { 1717 | this.options[i] = options[i]; 1718 | } 1719 | 1720 | this.sizeRatioX = 1; 1721 | this.sizeRatioY = 1; 1722 | this.maxPosX = 0; 1723 | this.maxPosY = 0; 1724 | 1725 | if ( this.options.interactive ) { 1726 | if ( !this.options.disableTouch ) { 1727 | utils.addEvent(this.indicator, 'touchstart', this); 1728 | utils.addEvent(window, 'touchend', this); 1729 | } 1730 | if ( !this.options.disablePointer ) { 1731 | utils.addEvent(this.indicator, utils.prefixPointerEvent('pointerdown'), this); 1732 | utils.addEvent(window, utils.prefixPointerEvent('pointerup'), this); 1733 | } 1734 | if ( !this.options.disableMouse ) { 1735 | utils.addEvent(this.indicator, 'mousedown', this); 1736 | utils.addEvent(window, 'mouseup', this); 1737 | } 1738 | } 1739 | 1740 | if ( this.options.fade ) { 1741 | this.wrapperStyle[utils.style.transform] = this.scroller.translateZ; 1742 | var durationProp = utils.style.transitionDuration; 1743 | if(!durationProp) { 1744 | return; 1745 | } 1746 | this.wrapperStyle[durationProp] = utils.isBadAndroid ? '0.0001ms' : '0ms'; 1747 | // remove 0.0001ms 1748 | var self = this; 1749 | if(utils.isBadAndroid) { 1750 | rAF(function() { 1751 | if(self.wrapperStyle[durationProp] === '0.0001ms') { 1752 | self.wrapperStyle[durationProp] = '0s'; 1753 | } 1754 | }); 1755 | } 1756 | this.wrapperStyle.opacity = '0'; 1757 | } 1758 | } 1759 | 1760 | Indicator.prototype = { 1761 | handleEvent: function (e) { 1762 | switch ( e.type ) { 1763 | case 'touchstart': 1764 | case 'pointerdown': 1765 | case 'MSPointerDown': 1766 | case 'mousedown': 1767 | this._start(e); 1768 | break; 1769 | case 'touchmove': 1770 | case 'pointermove': 1771 | case 'MSPointerMove': 1772 | case 'mousemove': 1773 | this._move(e); 1774 | break; 1775 | case 'touchend': 1776 | case 'pointerup': 1777 | case 'MSPointerUp': 1778 | case 'mouseup': 1779 | case 'touchcancel': 1780 | case 'pointercancel': 1781 | case 'MSPointerCancel': 1782 | case 'mousecancel': 1783 | this._end(e); 1784 | break; 1785 | } 1786 | }, 1787 | 1788 | destroy: function () { 1789 | if ( this.options.fadeScrollbars ) { 1790 | clearTimeout(this.fadeTimeout); 1791 | this.fadeTimeout = null; 1792 | } 1793 | if ( this.options.interactive ) { 1794 | utils.removeEvent(this.indicator, 'touchstart', this); 1795 | utils.removeEvent(this.indicator, utils.prefixPointerEvent('pointerdown'), this); 1796 | utils.removeEvent(this.indicator, 'mousedown', this); 1797 | 1798 | utils.removeEvent(window, 'touchmove', this); 1799 | utils.removeEvent(window, utils.prefixPointerEvent('pointermove'), this); 1800 | utils.removeEvent(window, 'mousemove', this); 1801 | 1802 | utils.removeEvent(window, 'touchend', this); 1803 | utils.removeEvent(window, utils.prefixPointerEvent('pointerup'), this); 1804 | utils.removeEvent(window, 'mouseup', this); 1805 | } 1806 | 1807 | if ( this.options.defaultScrollbars ) { 1808 | this.wrapper.parentNode.removeChild(this.wrapper); 1809 | } 1810 | }, 1811 | 1812 | _start: function (e) { 1813 | var point = e.touches ? e.touches[0] : e; 1814 | 1815 | e.preventDefault(); 1816 | e.stopPropagation(); 1817 | 1818 | this.transitionTime(); 1819 | 1820 | this.initiated = true; 1821 | this.moved = false; 1822 | this.lastPointX = point.pageX; 1823 | this.lastPointY = point.pageY; 1824 | 1825 | this.startTime = utils.getTime(); 1826 | 1827 | if ( !this.options.disableTouch ) { 1828 | utils.addEvent(window, 'touchmove', this); 1829 | } 1830 | if ( !this.options.disablePointer ) { 1831 | utils.addEvent(window, utils.prefixPointerEvent('pointermove'), this); 1832 | } 1833 | if ( !this.options.disableMouse ) { 1834 | utils.addEvent(window, 'mousemove', this); 1835 | } 1836 | 1837 | this.scroller._execEvent('beforeScrollStart'); 1838 | }, 1839 | 1840 | _move: function (e) { 1841 | var point = e.touches ? e.touches[0] : e, 1842 | deltaX, deltaY, 1843 | newX, newY, 1844 | timestamp = utils.getTime(); 1845 | 1846 | if ( !this.moved ) { 1847 | this.scroller._execEvent('scrollStart'); 1848 | } 1849 | 1850 | this.moved = true; 1851 | 1852 | deltaX = point.pageX - this.lastPointX; 1853 | this.lastPointX = point.pageX; 1854 | 1855 | deltaY = point.pageY - this.lastPointY; 1856 | this.lastPointY = point.pageY; 1857 | 1858 | newX = this.x + deltaX; 1859 | newY = this.y + deltaY; 1860 | 1861 | this._pos(newX, newY); 1862 | 1863 | // INSERT POINT: indicator._move 1864 | 1865 | e.preventDefault(); 1866 | e.stopPropagation(); 1867 | }, 1868 | 1869 | _end: function (e) { 1870 | if ( !this.initiated ) { 1871 | return; 1872 | } 1873 | 1874 | this.initiated = false; 1875 | 1876 | e.preventDefault(); 1877 | e.stopPropagation(); 1878 | 1879 | utils.removeEvent(window, 'touchmove', this); 1880 | utils.removeEvent(window, utils.prefixPointerEvent('pointermove'), this); 1881 | utils.removeEvent(window, 'mousemove', this); 1882 | 1883 | if ( this.scroller.options.snap ) { 1884 | var snap = this.scroller._nearestSnap(this.scroller.x, this.scroller.y); 1885 | 1886 | var time = this.options.snapSpeed || Math.max( 1887 | Math.max( 1888 | Math.min(Math.abs(this.scroller.x - snap.x), 1000), 1889 | Math.min(Math.abs(this.scroller.y - snap.y), 1000) 1890 | ), 300); 1891 | 1892 | if ( this.scroller.x != snap.x || this.scroller.y != snap.y ) { 1893 | this.scroller.directionX = 0; 1894 | this.scroller.directionY = 0; 1895 | this.scroller.currentPage = snap; 1896 | this.scroller.scrollTo(snap.x, snap.y, time, this.scroller.options.bounceEasing); 1897 | } 1898 | } 1899 | 1900 | if ( this.moved ) { 1901 | this.scroller._execEvent('scrollEnd'); 1902 | } 1903 | }, 1904 | 1905 | transitionTime: function (time) { 1906 | time = time || 0; 1907 | var durationProp = utils.style.transitionDuration; 1908 | if(!durationProp) { 1909 | return; 1910 | } 1911 | 1912 | this.indicatorStyle[durationProp] = time + 'ms'; 1913 | 1914 | if ( !time && utils.isBadAndroid ) { 1915 | this.indicatorStyle[durationProp] = '0.0001ms'; 1916 | // remove 0.0001ms 1917 | var self = this; 1918 | rAF(function() { 1919 | if(self.indicatorStyle[durationProp] === '0.0001ms') { 1920 | self.indicatorStyle[durationProp] = '0s'; 1921 | } 1922 | }); 1923 | } 1924 | }, 1925 | 1926 | transitionTimingFunction: function (easing) { 1927 | this.indicatorStyle[utils.style.transitionTimingFunction] = easing; 1928 | }, 1929 | 1930 | refresh: function () { 1931 | this.transitionTime(); 1932 | 1933 | if ( this.options.listenX && !this.options.listenY ) { 1934 | this.indicatorStyle.display = this.scroller.hasHorizontalScroll ? 'block' : 'none'; 1935 | } else if ( this.options.listenY && !this.options.listenX ) { 1936 | this.indicatorStyle.display = this.scroller.hasVerticalScroll ? 'block' : 'none'; 1937 | } else { 1938 | this.indicatorStyle.display = this.scroller.hasHorizontalScroll || this.scroller.hasVerticalScroll ? 'block' : 'none'; 1939 | } 1940 | 1941 | if ( this.scroller.hasHorizontalScroll && this.scroller.hasVerticalScroll ) { 1942 | utils.addClass(this.wrapper, 'iScrollBothScrollbars'); 1943 | utils.removeClass(this.wrapper, 'iScrollLoneScrollbar'); 1944 | 1945 | if ( this.options.defaultScrollbars && this.options.customStyle ) { 1946 | if ( this.options.listenX ) { 1947 | this.wrapper.style.right = '8px'; 1948 | } else { 1949 | this.wrapper.style.bottom = '8px'; 1950 | } 1951 | } 1952 | } else { 1953 | utils.removeClass(this.wrapper, 'iScrollBothScrollbars'); 1954 | utils.addClass(this.wrapper, 'iScrollLoneScrollbar'); 1955 | 1956 | if ( this.options.defaultScrollbars && this.options.customStyle ) { 1957 | if ( this.options.listenX ) { 1958 | this.wrapper.style.right = '2px'; 1959 | } else { 1960 | this.wrapper.style.bottom = '2px'; 1961 | } 1962 | } 1963 | } 1964 | 1965 | var r = this.wrapper.offsetHeight; // force refresh 1966 | 1967 | if ( this.options.listenX ) { 1968 | this.wrapperWidth = this.wrapper.clientWidth; 1969 | if ( this.options.resize ) { 1970 | this.indicatorWidth = Math.max(Math.round(this.wrapperWidth * this.wrapperWidth / (this.scroller.scrollerWidth || this.wrapperWidth || 1)), 8); 1971 | this.indicatorStyle.width = this.indicatorWidth + 'px'; 1972 | } else { 1973 | this.indicatorWidth = this.indicator.clientWidth; 1974 | } 1975 | 1976 | this.maxPosX = this.wrapperWidth - this.indicatorWidth; 1977 | 1978 | if ( this.options.shrink == 'clip' ) { 1979 | this.minBoundaryX = -this.indicatorWidth + 8; 1980 | this.maxBoundaryX = this.wrapperWidth - 8; 1981 | } else { 1982 | this.minBoundaryX = 0; 1983 | this.maxBoundaryX = this.maxPosX; 1984 | } 1985 | 1986 | this.sizeRatioX = this.options.speedRatioX || (this.scroller.maxScrollX && (this.maxPosX / this.scroller.maxScrollX)); 1987 | } 1988 | 1989 | if ( this.options.listenY ) { 1990 | this.wrapperHeight = this.wrapper.clientHeight; 1991 | if ( this.options.resize ) { 1992 | this.indicatorHeight = Math.max(Math.round(this.wrapperHeight * this.wrapperHeight / (this.scroller.scrollerHeight || this.wrapperHeight || 1)), 8); 1993 | this.indicatorStyle.height = this.indicatorHeight + 'px'; 1994 | } else { 1995 | this.indicatorHeight = this.indicator.clientHeight; 1996 | } 1997 | 1998 | this.maxPosY = this.wrapperHeight - this.indicatorHeight; 1999 | 2000 | if ( this.options.shrink == 'clip' ) { 2001 | this.minBoundaryY = -this.indicatorHeight + 8; 2002 | this.maxBoundaryY = this.wrapperHeight - 8; 2003 | } else { 2004 | this.minBoundaryY = 0; 2005 | this.maxBoundaryY = this.maxPosY; 2006 | } 2007 | 2008 | this.maxPosY = this.wrapperHeight - this.indicatorHeight; 2009 | this.sizeRatioY = this.options.speedRatioY || (this.scroller.maxScrollY && (this.maxPosY / this.scroller.maxScrollY)); 2010 | } 2011 | 2012 | this.updatePosition(); 2013 | }, 2014 | 2015 | updatePosition: function () { 2016 | var x = this.options.listenX && Math.round(this.sizeRatioX * this.scroller.x) || 0, 2017 | y = this.options.listenY && Math.round(this.sizeRatioY * this.scroller.y) || 0; 2018 | 2019 | if ( !this.options.ignoreBoundaries ) { 2020 | if ( x < this.minBoundaryX ) { 2021 | if ( this.options.shrink == 'scale' ) { 2022 | this.width = Math.max(this.indicatorWidth + x, 8); 2023 | this.indicatorStyle.width = this.width + 'px'; 2024 | } 2025 | x = this.minBoundaryX; 2026 | } else if ( x > this.maxBoundaryX ) { 2027 | if ( this.options.shrink == 'scale' ) { 2028 | this.width = Math.max(this.indicatorWidth - (x - this.maxPosX), 8); 2029 | this.indicatorStyle.width = this.width + 'px'; 2030 | x = this.maxPosX + this.indicatorWidth - this.width; 2031 | } else { 2032 | x = this.maxBoundaryX; 2033 | } 2034 | } else if ( this.options.shrink == 'scale' && this.width != this.indicatorWidth ) { 2035 | this.width = this.indicatorWidth; 2036 | this.indicatorStyle.width = this.width + 'px'; 2037 | } 2038 | 2039 | if ( y < this.minBoundaryY ) { 2040 | if ( this.options.shrink == 'scale' ) { 2041 | this.height = Math.max(this.indicatorHeight + y * 3, 8); 2042 | this.indicatorStyle.height = this.height + 'px'; 2043 | } 2044 | y = this.minBoundaryY; 2045 | } else if ( y > this.maxBoundaryY ) { 2046 | if ( this.options.shrink == 'scale' ) { 2047 | this.height = Math.max(this.indicatorHeight - (y - this.maxPosY) * 3, 8); 2048 | this.indicatorStyle.height = this.height + 'px'; 2049 | y = this.maxPosY + this.indicatorHeight - this.height; 2050 | } else { 2051 | y = this.maxBoundaryY; 2052 | } 2053 | } else if ( this.options.shrink == 'scale' && this.height != this.indicatorHeight ) { 2054 | this.height = this.indicatorHeight; 2055 | this.indicatorStyle.height = this.height + 'px'; 2056 | } 2057 | } 2058 | 2059 | this.x = x; 2060 | this.y = y; 2061 | 2062 | if ( this.scroller.options.useTransform ) { 2063 | this.indicatorStyle[utils.style.transform] = 'translate(' + x + 'px,' + y + 'px)' + this.scroller.translateZ; 2064 | } else { 2065 | this.indicatorStyle.left = x + 'px'; 2066 | this.indicatorStyle.top = y + 'px'; 2067 | } 2068 | }, 2069 | 2070 | _pos: function (x, y) { 2071 | if ( x < 0 ) { 2072 | x = 0; 2073 | } else if ( x > this.maxPosX ) { 2074 | x = this.maxPosX; 2075 | } 2076 | 2077 | if ( y < 0 ) { 2078 | y = 0; 2079 | } else if ( y > this.maxPosY ) { 2080 | y = this.maxPosY; 2081 | } 2082 | 2083 | x = this.options.listenX ? Math.round(x / this.sizeRatioX) : this.scroller.x; 2084 | y = this.options.listenY ? Math.round(y / this.sizeRatioY) : this.scroller.y; 2085 | 2086 | this.scroller.scrollTo(x, y); 2087 | }, 2088 | 2089 | fade: function (val, hold) { 2090 | if ( hold && !this.visible ) { 2091 | return; 2092 | } 2093 | 2094 | clearTimeout(this.fadeTimeout); 2095 | this.fadeTimeout = null; 2096 | 2097 | var time = val ? 250 : 500, 2098 | delay = val ? 0 : 300; 2099 | 2100 | val = val ? '1' : '0'; 2101 | 2102 | this.wrapperStyle[utils.style.transitionDuration] = time + 'ms'; 2103 | 2104 | this.fadeTimeout = setTimeout((function (val) { 2105 | this.wrapperStyle.opacity = val; 2106 | this.visible = +val; 2107 | }).bind(this, val), delay); 2108 | } 2109 | }; 2110 | 2111 | IScroll.utils = utils; 2112 | 2113 | if ( typeof module != 'undefined' && module.exports ) { 2114 | module.exports = IScroll; 2115 | } else if ( typeof define == 'function' && define.amd ) { 2116 | define( function () { return IScroll; } ); 2117 | } else { 2118 | window.IScroll = IScroll; 2119 | } 2120 | 2121 | })(window, document, Math); 2122 | --------------------------------------------------------------------------------