├── .gitattributes ├── .gitignore ├── README.md ├── app.asar ├── asar_pack.BAT ├── bin ├── css │ ├── index.css │ └── wave-doing.css ├── img │ ├── ICON.ico │ ├── ICON.png │ ├── cpu.svg │ ├── error.svg │ ├── file.svg │ ├── loading.svg │ ├── ok.svg │ └── remove.svg ├── index.html ├── lib │ ├── animate.css │ ├── jquery.min.js │ └── sine-waves.js ├── main.js ├── package.json ├── start.js └── vendor.bundle.js ├── components └── main-list.vue ├── index.js ├── limitPNG.js ├── move.bat ├── package.json ├── report.js ├── webpack.config.js └── website ├── ICON.ico ├── assets ├── css │ ├── admin.css │ ├── amazeui.css │ ├── amazeui.flat.css │ ├── amazeui.flat.min.css │ ├── amazeui.min.css │ └── app.css ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 ├── i │ ├── app-icon72x72@2x.png │ ├── examples │ │ ├── admin-chrome.png │ │ ├── admin-firefox.png │ │ ├── admin-ie.png │ │ ├── admin-opera.png │ │ ├── admin-safari.png │ │ ├── adminPage.png │ │ ├── blogPage.png │ │ ├── landing.png │ │ ├── landingPage.png │ │ ├── loginPage.png │ │ └── sidebarPage.png │ ├── favicon.png │ └── startup-640x1096.png └── js │ ├── amazeui.ie8polyfill.js │ ├── amazeui.ie8polyfill.min.js │ ├── amazeui.js │ ├── amazeui.min.js │ ├── amazeui.widgets.helper.js │ ├── amazeui.widgets.helper.min.js │ ├── app.js │ ├── handlebars.min.js │ └── jquery.min.js ├── css.css ├── img ├── 1_lim[lossy-low].png ├── cpu.svg ├── gif3.gif ├── logo-mini.png ├── logo.png ├── lossy.svg ├── no1.svg ├── option.svg ├── timecus.png ├── top.svg └── vs │ ├── PNG_lossy_vs.png │ ├── download.url │ ├── lossy.png │ ├── ss.png │ └── vuelogo.png ├── index.css ├── index.html ├── lib ├── animate.css ├── jquery.event.move.js ├── jquery.twentytwenty.js └── twentytwenty.css └── rrr.html /.gitattributes: -------------------------------------------------------------------------------- 1 | 2 | *.vue linguist-language=JavaScript 3 | 4 | *.css linguist-language=JavaScript 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # ========================= 3 | # 忽略难以上传的文件 4 | # ========================= 5 | test 6 | UI 7 | release 8 | electron-v1.2.0-win32-x64 9 | electron-v1.2.2-win32-x64 10 | .idea 11 | node_modules 12 | 13 | # Windows image file caches 14 | Thumbs.db 15 | ehthumbs.db 16 | 17 | # Folder config file 18 | Desktop.ini 19 | 20 | # Recycle Bin used on file shares 21 | $RECYCLE.BIN/ 22 | 23 | # Windows Installer files 24 | *.cab 25 | *.msi 26 | *.msm 27 | *.msp 28 | 29 | # Windows shortcuts 30 | *.lnk 31 | 32 | # ========================= 33 | # Operating System Files 34 | # ========================= 35 | 36 | # OSX 37 | # ========================= 38 | 39 | .DS_Store 40 | .AppleDouble 41 | .LSOverride 42 | 43 | # Thumbnails 44 | ._* 45 | 46 | # Files that might appear on external disk 47 | .Spotlight-V100 48 | .Trashes 49 | 50 | # Directories potentially created on remote AFP share 51 | .AppleDB 52 | .AppleDesktop 53 | Network Trash Folder 54 | Temporary Items 55 | .apdisk 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # limitPNG 2 |

3 | 4 |

5 | 6 | GUI PNG image compression tool ( GUI use Electron) 7 | 8 | [http://nullice.com/limitPNG/](http://nullice.com/limitPNG/) 9 | 10 | 这是个用 electron 开发的图形界面 PNG 图片压缩工具。 11 | 12 | 支持无损压缩和有损压缩。 13 | 14 | 比同类的 PNGGauntle 、 scriptPNG 、 Leanify 、 Caesium 能压缩得更小。 15 | 16 | 17 | ---- 18 | 19 | 20 | 21 | 22 | 23 | 24 | 使用以下工具: 25 | 26 | 实际上这是一个压缩工具集合,使用了以下压缩工具的二进制文件, 27 | 28 | compression Using tool binary list : 29 | - [zopflipng](https://github.com/google/zopfli) 30 | - [pngwolf](http://bjoern.hoehrmann.de/pngwolf/) 31 | - [pngquant](https://pngquant.org/) 32 | - [TruePNG](http://x128.ho.ua/pngutils.html) 33 | - [PNGOUT](http://advsys.net/ken/utils.htm#pngout) 34 | - [ect](http://css-ig.net/) 35 | 36 | ---- 37 | ### LICENSES 38 | **CC0 Public Domain** 39 | 40 | 这是一个共有领域作品,本人不保有此作品任何权益,任何人可以随意使用、修改和分发,不要求署名 41 | 42 | [![](http://ww3.sinaimg.cn/large/c35419f1gw1f4qhrttrtqj202g00v742.jpg)](http://creativecommons.org/publicdomain/zero/1.0) 43 | 44 | 45 | 46 | ----------- 47 | 48 | ##### 不显眼的功能: 49 | 50 | ![](http://ww4.sinaimg.cn/large/c35419f1gw1f4qkb9h82yj205k02edfn.jpg) 51 | 52 | 连续双击文件列表信息文字这里可以生成一个 html 格式的处理报告,有并在浏览器中打开,要保存请保存网页。 53 | 报告里面有详细的压缩信息,和使用压缩工具的流程日志: 54 | 55 | ![报告](http://ww3.sinaimg.cn/large/c35419f1gw1f4qkeragasj20zm09wdme.jpg) 56 | 57 | 58 | 59 | 60 | ![](http://ww2.sinaimg.cn/large/c35419f1gw1f4qkg4wfowj203d00o741.jpg) 61 | 62 | 右键窗口标题可以打开文件对比功能,选择一系列文件,会以第一个文件为原文件来计算大小,生成文件大小比较报告: 63 | ![](http://ww3.sinaimg.cn/large/c35419f1gw1f4qki0eadzj20j90akgo0.jpg) 64 | 65 | ![](http://ww2.sinaimg.cn/large/c35419f1gw1f4qklh0qa5j20lu079wgz.jpg) 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /app.asar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/app.asar -------------------------------------------------------------------------------- /asar_pack.BAT: -------------------------------------------------------------------------------- 1 | asar pack "E:\Work\GitHub\limitPNG\bin" app.asar 2 | 3 | pause -------------------------------------------------------------------------------- /bin/css/index.css: -------------------------------------------------------------------------------- 1 | 2 | button.do { 3 | display: block; 4 | cursor: pointer; 5 | outline: none; 6 | position: absolute; 7 | left: 0; 8 | right: 0; 9 | margin: auto; 10 | transition: all .3s cubic-bezier(0.74, 0.07, 0.53, 1.06); 11 | background: linear-gradient(70deg, #1ad7f9, #5eece4); 12 | width: 130px; 13 | border-radius: 50px; 14 | color: #fff; 15 | font-size: 16px; 16 | font-weight: lighter; 17 | font-family: "Microsoft YaHei","Hiragino Sans GB","ヒラギノ角ゴ ProN W3 Regular","WenQuanYi Micro Hei","Hiragino Kaku Gothic ProN","メイリオ",Helvetica,sans-serif; 18 | padding: 5px; 19 | border: none; 20 | } 21 | 22 | button.do:hover { 23 | transition: all .3s ease; 24 | margin-top: -2px; 25 | background: linear-gradient(20deg, #20cdff, #2dfff3); 26 | 27 | width: 135px; 28 | /* padding: 8px; */ 29 | box-shadow: 0 10px 20px #54f3ff; 30 | /* border-radius: 0px; */ 31 | } 32 | 33 | button.do:active { 34 | width: 140px; 35 | /* height: 30px; */ 36 | transition: all .1s cubic-bezier(0.74, 0.07, 0.53, 1.06); 37 | font-size: 14px; 38 | } 39 | 40 | /* 必需 */ 41 | .anime-transition { 42 | transition: all .4s ease; 43 | overflow: hidden; 44 | } 45 | 46 | .anime-enter, .anime-leave { 47 | height: 0; 48 | opacity: 0; 49 | } 50 | 51 | body { 52 | font-family: "Microsoft YaHei","Hiragino Sans GB","ヒラギノ角ゴ ProN W3 Regular","WenQuanYi Micro Hei","Hiragino Kaku Gothic ProN","メイリオ",Helvetica,sans-serif; 53 | margin: 0; 54 | height: 100%; 55 | width: 100%; 56 | position: absolute; 57 | overflow: hidden; 58 | 59 | } 60 | 61 | .item div:not(.anime) { 62 | display: inline-block; 63 | padding: 6px 18px; 64 | margin: 4px 8px; 65 | cursor: default; 66 | user-select: none; 67 | 68 | background-color: rgba(255, 255, 255, 0); 69 | color: rgba(64, 181, 202, 0.72); 70 | font-weight: lighter; 71 | -webkit-user-select: none; 72 | } 73 | 74 | .item div:not(.anime).name { 75 | margin-left: 0; 76 | padding-left: 36px; 77 | white-space: nowrap; 78 | overflow: hidden; 79 | text-overflow: ellipsis; 80 | } 81 | 82 | 83 | 84 | .item div.done_ico img, .item div.doing_ico img, .item div.error_ico img { 85 | width: 16px; 86 | height: 16px; 87 | position: absolute; 88 | top: 0; 89 | bottom: 0; 90 | margin: auto; 91 | } 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | .item div.doing_ico img { 100 | width: 14px; 101 | height: 14px; 102 | animation: fa-spin .65s infinite linear; 103 | } 104 | .item.anime-transition.error div { 105 | color: #ff8282; 106 | } 107 | @keyframes fa-spin { 108 | 0% { 109 | transform: rotate(0deg); 110 | } 111 | 112 | 100% { 113 | transform: rotate(360deg); 114 | } 115 | } 116 | 117 | .item div.done_ico, .item div.doing_ico ,.item div.error_ico{ 118 | padding: 0px; 119 | position: absolute; 120 | top: 0; 121 | bottom: 0; 122 | margin: auto; 123 | margin-left: 12px; 124 | } 125 | 126 | #list { 127 | position: absolute; 128 | width: 100%; 129 | top: 64px; 130 | bottom: 75px; 131 | overflow: overlay; 132 | 133 | /*-webkit-user-select: none;*/ 134 | } 135 | 136 | 137 | 138 | .item:first-child { 139 | margin-top: 8px; 140 | } 141 | 142 | .list_shadow_bottm { 143 | position: absolute; 144 | height: 20px; 145 | width: 100%; 146 | background: linear-gradient(0deg, #ffffff, rgba(255, 255, 255, 0)); 147 | z-index: 999; 148 | bottom: 74px; 149 | } 150 | 151 | .list_shadow_top { 152 | position: absolute; 153 | height: 10px; 154 | width: 100%; 155 | background: linear-gradient(180deg, #ffffff, rgba(255, 255, 255, 0)); 156 | z-index: 100; 157 | top: 63px; 158 | } 159 | 160 | .item div.name { 161 | margin-right: 220px; 162 | padding-left: 26px; 163 | display: block; 164 | position: relative; 165 | } 166 | 167 | .item div.wave_box { 168 | position: absolute; 169 | top: 20px; 170 | left: 50%; 171 | } 172 | 173 | .item div.size_old { 174 | position: absolute; 175 | right: 0; 176 | width: 85px; 177 | text-align: end; 178 | font-size: 14px; 179 | top: 0px; 180 | bottom: 0px; 181 | vertical-align: middle; 182 | padding: 2px 10px; 183 | margin: 10px 3px; 184 | } 185 | 186 | 187 | .cant_do{ 188 | opacity: .5; 189 | pointer-events: none; 190 | } 191 | 192 | .size_new { 193 | 194 | position: absolute; 195 | right: 90px; 196 | width: 130px; 197 | text-align: end; 198 | font-size: 14px; 199 | margin: 10px 10px; 200 | top: 0px; 201 | bottom: 0px; 202 | vertical-align: middle; 203 | padding: 2px 10px; 204 | white-space: nowrap; 205 | } 206 | 207 | .item { 208 | position: relative; 209 | margin-bottom: 0; 210 | background-color: rgba(221, 253, 236, 0); 211 | border-bottom: 1px solid rgba(85, 193, 224, 0.16); 212 | } 213 | 214 | #drag_file_showbox { 215 | background-color: rgba(0, 130, 101, 0.15); 216 | position: fixed; 217 | top: 2px; 218 | left: 2px; 219 | right: 2px; 220 | bottom: 2px; 221 | border: 2px dashed #68C1AE; 222 | display: none; 223 | } 224 | 225 | /*@-webkit-keyframes gogogo {*/ 226 | /*0% {*/ 227 | 228 | /*-webkit-transform: rotate(0deg);*/ 229 | /*border: 5px solid red;*/ 230 | 231 | /*}*/ 232 | /*50% {*/ 233 | /*-webkit-transform: rotate(180deg);*/ 234 | /*background: black;*/ 235 | /*border: 5px solid yellow;*/ 236 | /*}*/ 237 | /*100% {*/ 238 | /*-webkit-transform: rotate(360deg);*/ 239 | /*background: white;*/ 240 | /*border: 5px solid red;*/ 241 | /*}*/ 242 | 243 | /*}*/ 244 | 245 | select { 246 | position: absolute; 247 | font-size: 12px; 248 | margin-top: 2px; 249 | font-family: "Microsoft YaHei","Hiragino Sans GB","ヒラギノ角ゴ ProN W3 Regular","WenQuanYi Micro Hei","Hiragino Kaku Gothic ProN","メイリオ",Helvetica,sans-serif; 250 | font-weight: lighter; 251 | padding: 4px 5px; 252 | border-radius: 4px; 253 | border: 1px solid rgba(158, 217, 224, 0.59); 254 | color: #4ab5c1; 255 | width: 75px; 256 | outline: none; 257 | cursor: pointer; 258 | opacity: .8; 259 | } 260 | 261 | select.lift { 262 | margin-left: 2px; 263 | } 264 | 265 | select.right { 266 | right: 0px; 267 | width: 90px; 268 | margin-right: 2px; 269 | } 270 | 271 | select:hover { 272 | border: 1px solid #9ed9e0; 273 | opacity: 1; 274 | } 275 | 276 | optgroup { 277 | color: #6ba0af; 278 | } 279 | 280 | .op_p { 281 | color: #3e8088; 282 | font-size: 13px; 283 | } 284 | 285 | option.op_limit { 286 | color: #00b8d4; 287 | } 288 | 289 | option.op_high { 290 | color: #23c6e4; 291 | } 292 | 293 | option.op_quick { 294 | color: #41b9c5; 295 | } 296 | 297 | .bottom_bar { 298 | position: absolute; 299 | width: 95%; 300 | bottom: 46px; 301 | left: 0; 302 | margin: auto; 303 | right: 0; 304 | } 305 | 306 | .item div.size_new { 307 | padding-right: 0; 308 | font-weight: normal; 309 | } 310 | 311 | 312 | 313 | /* 设置滚动条的样式 */ 314 | ::-webkit-scrollbar { 315 | width: 10px; 316 | transition: all 1s; 317 | } 318 | 319 | 320 | /* 滚动槽 */ 321 | ::-webkit-scrollbar-track { 322 | border-radius: 10px; 323 | } 324 | 325 | /* 滚动条滑块 */ 326 | ::-webkit-scrollbar-thumb { 327 | border-radius: 10px; 328 | width: 11px; 329 | background: rgba(35, 205, 234, 0.23); 330 | border: 3px solid #fff; 331 | } 332 | 333 | ::-webkit-scrollbar-thumb:hover { 334 | transition: all 1s; 335 | border: none; 336 | } 337 | 338 | ::-webkit-scrollbar-thumb:hover { 339 | 340 | background: rgba(35, 205, 234, 0.33); 341 | } 342 | 343 | .loading_input_file img.icon{ 344 | width: 26px; 345 | height: 26px; 346 | animation: fa-spin .55s infinite; 347 | margin-bottom: -6px; 348 | position: relative; 349 | } 350 | 351 | .loading_input_file { 352 | position: fixed; 353 | width: 100%; 354 | height: 100%; 355 | background-color: rgba(255, 255, 255, 0.76); 356 | z-index: 9999; 357 | } 358 | 359 | .info { 360 | position: absolute; 361 | top: 33%; 362 | margin: auto; 363 | width: 70%; 364 | left: 0; 365 | right: 0; 366 | vertical-align: middle; 367 | text-align: center; 368 | color: #3b96b7; 369 | } 370 | .thumbnail.anime { 371 | z-index: -1; 372 | top: 3px; 373 | padding: 0; 374 | width: 26px; 375 | height:15px; 376 | overflow: hidden; 377 | margin: 0; 378 | display: inline-block; 379 | opacity: .8; 380 | cursor:pointer; 381 | 382 | } 383 | 384 | .thumbnail.anime:hover{ 385 | 386 | opacity: 1; 387 | box-shadow: 0 3px 5px #40e3e8; 388 | } 389 | 390 | .thumbnail.anime img { 391 | max-height: 30px; 392 | max-width: 40px; 393 | position: relative; 394 | margin-top: -50%; 395 | margin-left: 0; 396 | 397 | } 398 | 399 | .item div.size_new span { 400 | font-size: 10px; 401 | background: linear-gradient(30deg, rgba(46, 207, 236, 0.69), rgba(22, 214, 203, 0.94)); 402 | color: rgb(255, 255, 255); 403 | padding: 2px 7px; 404 | border-radius: 9px; 405 | } 406 | 407 | 408 | .menu_bar button img { 409 | width: 13px; 410 | height: 13px; 411 | 412 | } 413 | 414 | #remove_all img 415 | { 416 | width: 14px; 417 | height: 14px; 418 | margin-top: 2px; 419 | } 420 | 421 | 422 | 423 | #thread_number img 424 | { 425 | width: 16px; 426 | height: 16px; 427 | margin-top: 2px; 428 | 429 | } 430 | 431 | button#thread_number { 432 | opacity: .55; 433 | position: relative; 434 | } 435 | 436 | button#thread_number:hover { 437 | opacity: 1; 438 | } 439 | 440 | .menu_bar button,.window_nar button { 441 | background-color: rgba(255, 255, 255, 0); 442 | border: 1px solid rgba(255, 255, 255, 0); 443 | margin: 4px 0; 444 | outline: none; 445 | border-radius: 2px; 446 | cursor: pointer; 447 | opacity: .6; 448 | 449 | 450 | } 451 | 452 | 453 | .menu_bar button:hover, .window_nar button:hover{ 454 | opacity: 1; 455 | border: 1px solid #a0e1e2; 456 | } 457 | 458 | .menu_bar { 459 | position: absolute; 460 | width: 100%; 461 | background-color: white; 462 | top: 30px; 463 | z-index: 222; 464 | 465 | } 466 | 467 | #open_file{ 468 | margin-left: 10px; 469 | } 470 | 471 | .list_info { 472 | position: absolute; 473 | display: inline-block; 474 | right: 26px; 475 | font-size: 12px; 476 | color: #93d3da; 477 | margin-top: 7px; 478 | } 479 | 480 | div#vue_app { 481 | border-radius: 4px; 482 | background: #fff; 483 | box-shadow: 0 3px 7px rgba(21, 41, 43, 0.43); 484 | /*padding: 0px 10px;*/ 485 | position: absolute; 486 | top: 2px; 487 | bottom: 10px; 488 | left: 10px; 489 | right: 10px; 490 | 491 | } 492 | 493 | ::selection { 494 | background: rgba(126, 255, 247, 0.32); 495 | color: #93D3DA; 496 | border-radius: 3px; 497 | } 498 | 499 | 500 | 501 | div#drag_box { 502 | position: absolute; 503 | width: 100%; 504 | height: 100%; 505 | background: rebeccapurple; 506 | } 507 | 508 | #vue_app div{ -webkit-user-select: none;} 509 | 510 | #list,button,select,.menu_bar{ -webkit-app-region: no-drag;} 511 | 512 | #vue_app div.list_info{ 513 | -webkit-user-select: text; 514 | } 515 | 516 | .title { 517 | font-size: 11px; 518 | width: 84px; 519 | padding: 6px 12px; 520 | color: rgba(0, 156, 179, 0.58); 521 | cursor: pointer; 522 | white-space: nowrap; 523 | -webkit-user-select: none; 524 | -webkit-app-region: no-drag; 525 | } 526 | 527 | .title span.ver { 528 | font-size: 9px; 529 | color: rgba(49, 67, 70, 0.45); 530 | } 531 | 532 | .title:hover 533 | { 534 | animation-name: jello; 535 | transform-origin: 40% 20% ; 536 | animation-duration: .35s; 537 | animation-fill-mode: both; 538 | } 539 | 540 | select#select_thread { 541 | position: absolute; 542 | width: 28px; 543 | height: 20px; 544 | display: inline-block; 545 | top: 2px; 546 | left: 0px; 547 | opacity: 0; 548 | } 549 | 550 | .window_nar { 551 | position: absolute; 552 | right: 10px; 553 | z-index: 555; 554 | top: 0px; 555 | } 556 | 557 | 558 | .window_nar button { 559 | color: #2caebd; 560 | width: 30px; 561 | background-color: #fff; 562 | border: 1px solid rgba(158, 217, 224, 0.0); 563 | } 564 | 565 | div#welcome { 566 | position: absolute; 567 | left: 0; 568 | right: 0; 569 | margin: auto; 570 | text-align: center; 571 | margin-top: 40%; 572 | color: rgba(103, 212, 212, 0.39); 573 | font-weight: lighter; 574 | font-size: 20px; 575 | } -------------------------------------------------------------------------------- /bin/css/wave-doing.css: -------------------------------------------------------------------------------- 1 | 2 | .wave { 3 | border: 1px solid #1ee3ff; 4 | z-index: -23; 5 | opacity: .4; 6 | position: absolute; 7 | top: 3%; 8 | left: 50%; 9 | background: linear-gradient(70deg, rgba(26, 215, 249, 0.22), rgba(94, 236, 228, 0.2)); 10 | width: 2500px; 11 | height: 2500px; 12 | margin-left: -1250px; 13 | margin-top: 100px; 14 | -webkit-transform-origin: 50% 48%; 15 | transform-origin: 50% 48%; 16 | border-radius: 40%; 17 | animation: drift 8000ms infinite linear; 18 | } 19 | 20 | 21 | .wave.-five { 22 | animation: drift 3000ms infinite linear; 23 | } 24 | 25 | .wave.-four { 26 | animation: drift 4000ms infinite linear; 27 | } 28 | 29 | .wave.-three { 30 | 31 | animation: drift 5000ms infinite linear; 32 | } 33 | 34 | .wave.-two { 35 | animation: drift 7000ms infinite linear; 36 | opacity: .3; 37 | background: rgba(56, 255, 220, 0.19); 38 | } 39 | 40 | 41 | @keyframes drift { 42 | from { 43 | transform: rotate(0deg); 44 | } 45 | from { 46 | transform: rotate(360deg); 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /bin/img/ICON.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/bin/img/ICON.ico -------------------------------------------------------------------------------- /bin/img/ICON.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/bin/img/ICON.png -------------------------------------------------------------------------------- /bin/img/cpu.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bin/img/error.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bin/img/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bin/img/loading.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bin/img/ok.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bin/img/remove.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | limitPNG 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 |
limitPNG BETA5
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | 32 |
33 | 51 | 52 |
53 |
54 | 55 | 56 | 57 |
拖入 PNG 图片进来吧
58 | 59 |
60 |
61 | 62 | 正在载入 PNG 图片,会扫描子目录中的 PNG 图片... 63 |
64 |
65 | 66 |
67 | 68 |
69 | 70 |
71 |
72 |
73 |
74 |
77 |
78 | {{z.name}} 79 |
80 |
{{z.size_old | filter_file_size }}
81 |
{{z.size_new | filter_file_size }} ← 83 | {{z.size_new/z.size_old | filter_file_size_pre }} 84 |
85 | 86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | 96 | 97 | 98 |
99 |
100 |
101 | 102 | 120 | 121 | 122 | 123 | 130 |
131 |
132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /bin/lib/sine-waves.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/bin/lib/sine-waves.js -------------------------------------------------------------------------------- /bin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "LimitPNG", 3 | "version" : "0.1.0", 4 | "main" : "start.js" 5 | } -------------------------------------------------------------------------------- /bin/start.js: -------------------------------------------------------------------------------- 1 | const electron = require('electron'); 2 | const path = require('path'); 3 | const ipcMain = electron.ipcMain 4 | const Tray = electron.Tray; 5 | // Module to control application life. 6 | const {app} = electron; 7 | // Module to create native browser window. 8 | const {BrowserWindow} = electron; 9 | 10 | // Keep a global reference of the window object, if you don't, the window will 11 | // be closed automatically when the JavaScript object is garbage collected. 12 | let win; 13 | console.log(path.resolve()); 14 | function createWindow() { 15 | // Create the browser window. 16 | 17 | 18 | win = new BrowserWindow({ 19 | width: 500, 20 | height: 600, 21 | frame: false, //窗口边框 22 | transparent: true, //透明背景 23 | resizable: true,//可调边框 24 | minWidth:420, 25 | minHeight :250, 26 | title:"limitPNG", 27 | icon: path.join(__dirname,'/img/ICON.ico') 28 | 29 | }); 30 | 31 | // and load the index.html of the app. 32 | win.loadURL(`file://${__dirname}/index.html`); 33 | 34 | // Open the DevTools. 35 | // win.webContents.openDevTools(); 36 | 37 | // Emitted when the window is closed. 38 | win.on('closed', () => { 39 | // Dereference the window object, usually you would store windows 40 | // in an array if your app supports multi windows, this is the time 41 | // when you should delete the corresponding element. 42 | 43 | 44 | 45 | 46 | win = null; 47 | }); 48 | } 49 | 50 | // This method will be called when Electron has finished 51 | // initialization and is ready to create browser windows. 52 | // Some APIs can only be used after this event occurs. 53 | app.on('ready', createWindow); 54 | 55 | // Quit when all windows are closed. 56 | app.on('window-all-closed', () => { 57 | // On OS X it is common for applications and their menu bar 58 | // to stay active until the user quits explicitly with Cmd + Q 59 | if (process.platform !== 'darwin') { 60 | app.quit(); 61 | } 62 | }); 63 | 64 | app.on('activate', () => { 65 | // On OS X it's common to re-create a window in the app when the 66 | // dock icon is clicked and there are no other windows open. 67 | if (win === null) { 68 | createWindow(); 69 | } 70 | }); 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | // In this file you can include the rest of your app's specific main process 80 | // code. You can also put them in separate files and require them here. 81 | 82 | 83 | 84 | 85 | ipcMain.on('exit', function () { 86 | app.quit(); 87 | }); 88 | 89 | 90 | ipcMain.on('minimize', function () { 91 | win.minimize() 92 | }); -------------------------------------------------------------------------------- /components/main-list.vue: -------------------------------------------------------------------------------- 1 | 6 | 11 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by bgllj on 2016/5/31. 3 | */ 4 | 5 | import Vue from "vue"; 6 | import VueDragula from 'vue-dragula'; 7 | 8 | Vue.use(VueDragula); 9 | import "vue-dragula/styles/dragula.min.css" 10 | import path from "path" 11 | import fs from "fs" 12 | import fsex from "fs-extra"; 13 | import "./limitPNG.js" 14 | import "./report" 15 | import electron from 'electron' 16 | var remote = electron.remote; 17 | var dialog = remote.dialog; 18 | var shell = electron.shell; 19 | var ipcRenderer = electron.ipcRenderer; 20 | var shell = electron.shell; 21 | 22 | 23 | window.path = path; 24 | window.fs = fs; 25 | window.remote = remote; 26 | window.dialog = dialog; 27 | window.shell = shell 28 | window.Vue = Vue; 29 | 30 | 31 | window.filter_file_size = function (value) 32 | { 33 | if (value < 1024) 34 | { 35 | return value + " B"; 36 | } 37 | 38 | let z = (value / 1024); 39 | if (z > 1000) 40 | { 41 | return (z / 1024).toFixed(2) + " MB"; 42 | } 43 | else 44 | { 45 | return (z).toFixed(2) + " KB"; 46 | } 47 | } 48 | 49 | Vue.filter('filter_file_size', filter_file_size) 50 | 51 | 52 | window.filter_file_size_pre = function (value) 53 | { 54 | let pre; 55 | if (value > 1.001) 56 | { 57 | return "+ " + Math.floor((value - 1) * 100) + "%" 58 | } else 59 | { 60 | pre = (1 - value) 61 | if (pre < 0.01 && pre > 0) 62 | { 63 | return "- " + (pre * 100).toFixed(2) + "%"; 64 | } else 65 | { 66 | return "- " + Math.floor(pre * 100) + "%"; 67 | } 68 | 69 | } 70 | } 71 | 72 | Vue.filter('filter_file_size_pre', filter_file_size_pre) 73 | 74 | 75 | var file_data = []; 76 | 77 | //todo test 78 | // file_data 79 | // = [{ 80 | // name: "tas都是ss1.png", 81 | // size_old: "21233", 82 | // size_new: null, 83 | // doing: true 84 | // }, 85 | // { 86 | // name: "QWEERsadf.png", 87 | // size_old: "30000", 88 | // size_new: null 89 | // }, 90 | // { 91 | // name: "vvvvv.png", 92 | // size_old: "3233", 93 | // size_new: "5000", 94 | // done: true 95 | // 96 | // }, 97 | // { 98 | // name: "sequence-123.png", 99 | // size_old: "2133233", 100 | // size_new: "193133", 101 | // done: true 102 | // }, 103 | // { 104 | // name: "DDS. L'Huilier.png", 105 | // size_old: "2133233", 106 | // size_new: null, 107 | // error: true 108 | // } 109 | // ] 110 | 111 | 112 | var main_list = new Vue( 113 | { 114 | el: "#vue_app", 115 | data: { 116 | test: "ddddd", 117 | list: file_data, 118 | loading_file: false, 119 | tasks_doing: false, 120 | time: 0, 121 | 122 | 123 | //---- 124 | mode: "limit", 125 | out: "-suffix", 126 | thread: 2, 127 | 128 | }, 129 | methods: { 130 | 131 | openImg_right: function (e, msg) 132 | { 133 | if (e.button == 2) 134 | shell.openItem(msg) 135 | }, 136 | 137 | openImg: function (msg) 138 | { 139 | shell.openItem(msg) 140 | }, 141 | open_file: function () 142 | { 143 | if (v.tasks_doing) 144 | { 145 | alert("任务进行中,不能打开文件") 146 | return; 147 | } 148 | 149 | dialog.showOpenDialog( 150 | {title: "打开文件", properties: ["openFile", "multiSelections"]}, 151 | (files)=> 152 | { 153 | if (files != undefined && files.length > 0) 154 | { 155 | var newList = []; 156 | for (let i = 0; i < files.length; i++) 157 | { 158 | var stat = fs.lstatSync(files[i]); 159 | if (stat.isDirectory()) 160 | { 161 | _scanFiles(files[i], fs.readdirSync(files[i]), newList); 162 | } 163 | else 164 | { 165 | if (ifPngFile(files[i])) 166 | { 167 | newList.push({path: files[i], size: getFileSize(files[i])}); 168 | } 169 | } 170 | } 171 | _put_list(newList); 172 | } 173 | } 174 | ) 175 | }, 176 | remove_all: function () 177 | { 178 | if (v.tasks_doing == false) 179 | { 180 | this.list = []; 181 | file_data = []; 182 | } 183 | } 184 | 185 | 186 | }, 187 | computed: { 188 | // 一个计算属性的 getter 189 | list_size_old: function () 190 | { 191 | let size = 0; 192 | for (let i = 0; i < this.list.length; i++) 193 | { 194 | size = size + +this.list[i].size_old; 195 | } 196 | return size; 197 | }, 198 | list_size_new: function () 199 | { 200 | let size = 0; 201 | for (let i = 0; i < this.list.length; i++) 202 | { 203 | size = size + +this.list[i].size_new; 204 | } 205 | return size; 206 | }, 207 | out_dir: function () 208 | { 209 | if (this.out[0] != undefined && this.out[0] != "-") 210 | { 211 | return this.out; 212 | } else 213 | { 214 | return ""; 215 | } 216 | 217 | } 218 | } 219 | , 220 | created: function () 221 | { 222 | Vue.vueDragula.options('my-bag', { 223 | direction: 'vertical' 224 | }) 225 | } 226 | 227 | } 228 | ) 229 | 230 | 231 | window.v = main_list 232 | window.a = file_data 233 | 234 | const holder = document.getElementById('main_body'); 235 | 236 | holder.ondragover = () => 237 | { 238 | return false; 239 | }; 240 | holder.ondragleave = holder.ondragend = () => 241 | { 242 | return false; 243 | }; 244 | holder.ondrop = (e) => 245 | { 246 | e.preventDefault(); 247 | const file = e.dataTransfer.files[0]; 248 | console.log(e.dataTransfer.files); 249 | // console.log('File you dragged here is', file.path); 250 | main_list.loading_file = true; 251 | 252 | var ff = e.dataTransfer.files 253 | setTimeout(function () 254 | { 255 | putFile2list(ff); 256 | main_list.loading_file = false; 257 | }, 100) 258 | 259 | 260 | return false; 261 | }; 262 | 263 | 264 | function putFile2list(files) 265 | { 266 | 267 | if (files == undefined || files.length < 1) 268 | { 269 | return 270 | } 271 | 272 | var newList = []; 273 | 274 | for (let i = 0; i < files.length; i++) 275 | { 276 | var stat = fs.lstatSync(files[i].path); 277 | if (stat.isDirectory()) 278 | { 279 | _scanFiles(files[i].path, fs.readdirSync(files[i].path), newList); 280 | } 281 | else 282 | { 283 | if (ifPngFile(files[i].path)) 284 | { 285 | newList.push({path: files[i].path, size: files[i].size}); 286 | } 287 | 288 | } 289 | } 290 | 291 | _put_list(newList); 292 | console.log("--------") 293 | console.log(newList) 294 | // file_data 295 | } 296 | 297 | function _put_list(newList) 298 | { 299 | if (v.tasks_doing) 300 | { 301 | alert("任务进行中,不能添加文件") 302 | return; 303 | } else 304 | { 305 | 306 | if (file_data.length > 0) 307 | { 308 | if (file_data[0].done == true) 309 | { 310 | file_data = []; 311 | } 312 | } 313 | 314 | 315 | } 316 | 317 | 318 | for (let i = 0; i < newList.length; i++) 319 | { 320 | 321 | if (_existPath(newList[i].path,file_data) == false) 322 | { 323 | file_data.push({ 324 | name: path.basename(newList[i].path), 325 | size_old: newList[i].size, 326 | size_new: 0, 327 | path: newList[i].path, 328 | new_File: "", 329 | doing: false, 330 | done: false, 331 | error: false, 332 | error_info: "", 333 | time_consum: null, 334 | log: "", 335 | 336 | }); 337 | } 338 | 339 | 340 | } 341 | 342 | 343 | if (file_data !== undefined && file_data.length > 0) 344 | { 345 | main_list.list = file_data; 346 | 347 | } else 348 | { 349 | alert("拖入的文件没有图片") 350 | } 351 | 352 | 353 | function _existPath(_path, objArray) 354 | { 355 | for (let i = 0; i < objArray.length; i++) 356 | { 357 | if (objArray[i].path == _path) 358 | { 359 | return true; 360 | } 361 | } 362 | 363 | return false; 364 | } 365 | 366 | } 367 | function _scanFiles(dirPath, _files, newList) 368 | { 369 | for (let i = 0; i < _files.length; i++) 370 | { 371 | let _path = path.join(dirPath, _files[i]) 372 | 373 | let stat = fs.lstatSync(_path); 374 | if (stat.isDirectory()) 375 | { 376 | _scanFiles(_path, fs.readdirSync(_path), newList); 377 | } 378 | else 379 | { 380 | if (ifPngFile(_path)) 381 | { 382 | newList.push({path: _path, size: stat.size}); 383 | } 384 | } 385 | } 386 | } 387 | 388 | function ifPngFile(fileName) 389 | { 390 | if (path.extname(fileName).toLocaleLowerCase() == ".png") 391 | { 392 | return true 393 | } else 394 | { 395 | return false 396 | } 397 | } 398 | 399 | 400 | window.doLimiteByfile_data = function () 401 | { 402 | if (v.tasks_doing > 0) 403 | { 404 | return 405 | } else 406 | { 407 | 408 | } 409 | 410 | v.time = Date.now(); 411 | var tasks = [] 412 | window.tasks = tasks; 413 | for (let i = 0; i < main_list.list.length; i++) 414 | { 415 | let exp = new LimitPNG(main_list.list[i].path); 416 | console.log(exp) 417 | 418 | if (exp.pngFile != "") 419 | { 420 | tasks[i] = function (resolve, reject) 421 | { 422 | main_list.list[i].doing = true; 423 | let _do_func = function (newFile, old_size, new_size, time_consum, err, log) 424 | { 425 | main_list.list[i].doing = false; 426 | main_list.list[i].done = true; 427 | main_list.list[i].size_new = new_size; 428 | main_list.list[i].time_consum = time_consum; 429 | main_list.list[i].new_File = newFile; 430 | main_list.list[i].log = log; 431 | if (err != undefined && err != "") 432 | { 433 | main_list.list[i].error = true; 434 | main_list.list[i].error_info = err; 435 | } 436 | v.tasks_doing = v.tasks_doing - 1; 437 | if (v.tasks_doing < 1) 438 | { 439 | v.time = (Date.now() - v.time) 440 | } 441 | if (resolve != undefined) 442 | { 443 | resolve(0); 444 | } 445 | } 446 | exp.doDefault(main_list.out, main_list.mode, _do_func); 447 | }; 448 | } 449 | } 450 | 451 | 452 | //单线程 453 | // _do_tasks(tasks) 454 | 455 | 456 | //多线程 457 | // function _do_tasks_N(n) 458 | // { 459 | // for (let i = 0; i < tasks.length / n; i++) 460 | // { 461 | // _do_tasks(tasks.slice(n*i , n*i +n)) 462 | // } 463 | // } 464 | 465 | 466 | var stack = 2; 467 | if (v.thread != undefined && v.thread > 0 && v.thread < 16) 468 | { 469 | stack = v.thread; 470 | } 471 | 472 | v.tasks_doing = tasks.length; 473 | cheakTasks() 474 | function cheakTasks() 475 | { 476 | 477 | while (stack > 0 && tasks.length > 0) 478 | { 479 | tasks.shift()(function () 480 | { 481 | stack++; 482 | cheakTasks(); 483 | }); 484 | stack--; 485 | } 486 | 487 | } 488 | 489 | 490 | //双线程 491 | // var _task_piceA=[]; 492 | // var _task_piceB=[]; 493 | // for (let i = 0; i < tasks.length; i++) 494 | // { 495 | // if (i%2 ==0) 496 | // { 497 | // _task_piceB.push(tasks[i]); 498 | // }else 499 | // { 500 | // _task_piceA.push(tasks[i]); 501 | // } 502 | // } 503 | // 504 | // _do_tasks(_task_piceA); 505 | // _do_tasks(_task_piceB); 506 | 507 | 508 | function _do_tasks(_tasks) 509 | { 510 | let promises = []; 511 | for (let i = 0; i < _tasks.length; i++) 512 | { 513 | if (i == 0) 514 | { 515 | promises[0] = new Promise(_tasks[0]) 516 | } 517 | else 518 | { 519 | promises[i] = new Promise(function (resolve, reject) 520 | { 521 | promises[i - 1].then(function () {_tasks[i](resolve, reject);}) 522 | }) 523 | } 524 | } 525 | } 526 | 527 | } 528 | 529 | 530 | window.minimize = ()=> 531 | { 532 | ipcRenderer.send('minimize'); 533 | } 534 | 535 | window.exit = ()=> 536 | { 537 | let temp = os.tmpdir(); //"C:\Users\nullice\AppData\Local\Temp" 538 | temp = path.join(temp, "limitPNG_temp"); 539 | fsex.removeSync(temp) 540 | 541 | ipcRenderer.send('exit'); 542 | } 543 | 544 | 545 | var _select_out_op = false; 546 | 547 | 548 | window.select_out_blur = ()=> 549 | { 550 | if (_select_out_op = true) 551 | { 552 | _select_out_op = false; 553 | } 554 | } 555 | 556 | window.select_out = ()=> 557 | { 558 | if (_select_out_op) 559 | { 560 | var s = document.getElementById("outFile"); 561 | if (s.selectedIndex == 2) 562 | { 563 | dialog.showOpenDialog( 564 | {title: "选择输出文件夹", defaultPath: v.our_dir, properties: ["openDirectory"]}, 565 | (e)=> 566 | { 567 | if (e != undefined && e.length > 0) 568 | { 569 | 570 | s[2].text = "输出到:" + e[0]; 571 | s[2].value = e[0] 572 | v.out = e[0]; 573 | } 574 | } 575 | ) 576 | } 577 | } 578 | 579 | _select_out_op = !_select_out_op; 580 | 581 | } 582 | 583 | 584 | window.openUrl = function (url) 585 | { 586 | console.log(url) 587 | shell.openExternal(url); 588 | } 589 | 590 | 591 | -------------------------------------------------------------------------------- /limitPNG.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by bgllj on 2016/6/3. 3 | */ 4 | 5 | import child_process from "child_process"; 6 | import path from "path"; 7 | import os from "os"; 8 | import fs from "fs" 9 | import fsex from "fs-extra"; 10 | 11 | var exec = child_process.exec; 12 | 13 | window.child_process = child_process; 14 | window.exec = exec; 15 | window.os = os; 16 | 17 | 18 | function log(_in) 19 | { 20 | console.log(_in) 21 | } 22 | 23 | log.err = function (_in) 24 | { 25 | console.error(_in); 26 | } 27 | log.info = function (_in) 28 | { 29 | console.info(_in); 30 | } 31 | 32 | log.test = function (_in) 33 | { 34 | console.info(_in); 35 | } 36 | 37 | 38 | window.LimitPNG = LimitPNG; 39 | 40 | function LimitPNG(in_pngFile) 41 | { 42 | this.pngFile = ""; 43 | 44 | if (in_pngFile != undefined) 45 | { 46 | if (fileExists(in_pngFile)) 47 | { 48 | this.pngFile = path.resolve(in_pngFile); 49 | this.tempFile = this.allocTemp(); 50 | // this.color = this.getPngColorInfo(); 51 | } 52 | else 53 | { 54 | console.error("file not exist:" + in_pngFile) 55 | } 56 | 57 | 58 | } 59 | 60 | return this; 61 | } 62 | 63 | 64 | LimitPNG.prototype.doDefault = function (option, mode, callBack) 65 | { 66 | //this.do([PROCE.truePNG(1), PROCE.pngout(2), PROCE.cryopng(1), PROCE.pngwolf(1),PROCE.zopflipng(1)]) // 移除PROCE.cryopng(1) 67 | if (mode == "limit") 68 | { 69 | this.do([PROCE.truePNG(1), PROCE.pngout(2), PROCE.pngwolf(1), PROCE.zopflipng(1)], option, callBack) 70 | } 71 | else if (mode == "high") 72 | { 73 | this.do([PROCE.pngwolf(1), PROCE.ect(0)], option, callBack) 74 | } 75 | else if (mode == "quick") 76 | { 77 | this.do([PROCE.ect(0)], option, callBack) 78 | } 79 | else if (mode == "256-high") 80 | { 81 | this.do([PROCE.pngquant(22, true), PROCE.truePNG(1), PROCE.pngout(2), PROCE.pngwolf(1), PROCE.zopflipng(1)], option, callBack) 82 | } 83 | else if (mode == "256-low") 84 | { 85 | this.do([PROCE.pngquant(24, true), PROCE.truePNG(1), PROCE.pngout(2), PROCE.pngwolf(1), PROCE.zopflipng(1)], option, callBack) 86 | } 87 | else if (mode == "256-lowest") 88 | { 89 | this.do([PROCE.pngquant(25, true), PROCE.truePNG(1), PROCE.pngout(0, true), PROCE.pngwolf(1), PROCE.zopflipng(1)], option, callBack) 90 | } 91 | else if (mode == "lossy-high") 92 | { 93 | this.do([PROCE.pngquant(26, true), PROCE.truePNG(1), PROCE.pngout(0, true), PROCE.pngwolf(1), PROCE.zopflipng(1)], option, callBack) 94 | } 95 | else if (mode == "lossy-low") 96 | { 97 | this.do([PROCE.pngquant(27, true), PROCE.truePNG(1), PROCE.pngout(0, true), PROCE.pngwolf(1), PROCE.zopflipng(1)], option, callBack) 98 | } 99 | else if (mode == "lossy-quick") 100 | { 101 | this.do([PROCE.pngquant(26, true), PROCE.ect(0)], option, callBack) 102 | } 103 | 104 | 105 | } 106 | 107 | LimitPNG.prototype.doDefaultLossy = function () 108 | { 109 | this.do([PROCE.pngquant(3, true), PROCE.truePNG(1), PROCE.pngout(2), PROCE.pngwolf(1), PROCE.zopflipng(1)]) 110 | } 111 | 112 | 113 | LimitPNG.prototype.do = function (in_processors, outOption, callBack) 114 | { 115 | var sizeLog = ""; 116 | if (in_processors == undefined) 117 | { 118 | return null; 119 | } 120 | try 121 | { 122 | //fs.createReadStream(this.pngFile).pipe(fs.createWriteStream(this.tempFile)); 123 | fsex.copySync(this.pngFile, this.tempFile); 124 | } catch (e) 125 | { 126 | console.error(e) 127 | return null; 128 | } 129 | 130 | 131 | //fsex.copySync(this.pngFile, this.tempFile); 132 | //fs.rename(this.tempFile, this.tempFile+"2222222"); 133 | 134 | 135 | function _doChild(_proce, _tempFile, _i, resolve, reject) 136 | { 137 | try 138 | { 139 | log(_i + " ===================================") 140 | let do_cmd = '"' + path.resolve(".\\resources\\tools\\" + _proce.exeName) + '" ' + _proce.cmd(_tempFile); 141 | log("do_cmd " + _i + " : " + do_cmd); 142 | let child = child_process.exec(do_cmd, {timeout: 3654321}, function (error, stdout, stderr) 143 | { 144 | log.test("sout:" + _i + "\n" + stdout); 145 | if (error != undefined) 146 | { 147 | console.error("serror:" + _i + "\n" + error); 148 | } 149 | sizeLog = sizeLog + "->[" + _proce.exeName + " : " + getFileSize(_tempFile) + "]" 150 | log("sizeLog:" + _i + "\n" + sizeLog); 151 | resolve(_i); 152 | }); 153 | 154 | 155 | child.stdout.on('data', (data) => 156 | { 157 | log.test(`stdout: ${data}`); 158 | }); 159 | 160 | 161 | } 162 | catch (e) 163 | { 164 | 165 | log("catch-e:") 166 | log.err(e) 167 | sizeLog = sizeLog + "->[" + _proce.exeName + " : " + getFileSize(_tempFile) + "]" 168 | log("sizeLog:" + _i + "\n" + sizeLog); 169 | if (fileExists(_tempFile) != true) 170 | { 171 | reject(_i); 172 | } 173 | } 174 | } 175 | 176 | 177 | var _tempFile = this.tempFile; 178 | var _pngFile = this.pngFile; 179 | var old_size = getFileSize(_tempFile); 180 | var time_start = Date.now(); 181 | var promises = []; 182 | var funcs = []; 183 | for (let i = 0; i < in_processors.length; i++) 184 | { 185 | log(i + "--发出") 186 | let _tempFile = this.tempFile; 187 | 188 | if (i == 0) 189 | { 190 | promises[i] = new Promise(function (resolve, reject) 191 | { 192 | _doChild(in_processors[i], _tempFile, i, resolve, reject); 193 | }); 194 | 195 | } else 196 | { 197 | promises[i] = new Promise(function (resolve, reject) 198 | { 199 | promises[i - 1].then( 200 | function (value) 201 | { 202 | _doChild(in_processors[i], _tempFile, i, resolve, reject); 203 | } 204 | ) 205 | }) 206 | 207 | } 208 | 209 | } 210 | 211 | 212 | promises[promises.length - 1].then( 213 | function (value) 214 | { 215 | var new_size = getFileSize(_tempFile); 216 | var time_consum = Date.now() - time_start; 217 | var err = ""; 218 | 219 | 220 | if (new_size > old_size) 221 | { 222 | new_size = old_size; 223 | _tempFile =_pngFile; 224 | } 225 | 226 | 227 | if (outOption == "-overwrite") 228 | { 229 | try 230 | { 231 | fs.createReadStream(_tempFile).pipe(fs.createWriteStream(_pngFile)); 232 | } catch (e) 233 | { 234 | console.error(e) 235 | err = "无法写入到原文件。" + e 236 | callBack(_pngFile, old_size, new_size, time_consum, err); 237 | return null; 238 | } 239 | } 240 | else if (outOption == "-suffix") 241 | { 242 | var new_path = path.join(path.dirname(_pngFile), path.parse(_pngFile).name + `_lim[${v.mode}].png`) 243 | try 244 | { 245 | fs.createReadStream(_tempFile).pipe(fs.createWriteStream(new_path)); 246 | } catch (e) 247 | { 248 | console.error(e) 249 | err = "无法写入到文件 -suffix。" + e 250 | callBack(new_path, old_size, new_size, time_consum, err); 251 | return null; 252 | } 253 | } 254 | else if (fileExists(outOption)) 255 | { 256 | var new_path = path.join(outOption, path.parse(_pngFile).base) 257 | 258 | try 259 | { 260 | fs.createReadStream(_tempFile).pipe(fs.createWriteStream(new_path)); 261 | } catch (e) 262 | { 263 | console.error(e) 264 | err = "无法写入到文件 -out。" + e 265 | callBack(new_path, old_size, new_size, time_consum, err); 266 | return null; 267 | } 268 | } 269 | 270 | 271 | log("sizeLog:\n" + sizeLog); 272 | log("DONE===================================") 273 | 274 | 275 | callBack(new_path, old_size, new_size, time_consum, err, sizeLog); 276 | return 0; 277 | } 278 | ) 279 | 280 | } 281 | 282 | 283 | LimitPNG.prototype.allocTemp = function () 284 | { 285 | let temp = os.tmpdir(); //"C:\Users\nullice\AppData\Local\Temp" 286 | temp = path.join(temp, "limitPNG_temp"); 287 | 288 | 289 | if (fileExists(temp) == false) 290 | { 291 | fs.mkdirSync(temp) 292 | } 293 | if (fileExists(temp) == false) 294 | { 295 | console.error("无法创建临时文件夹 :" + temp); 296 | return null 297 | } 298 | 299 | 300 | temp = path.join(temp, Date.now() + "_" + path.basename(this.pngFile)); 301 | return temp; 302 | } 303 | 304 | 305 | LimitPNG.prototype.getPngColorInfo = function ()//By Pngout 306 | { 307 | //let outInfo = child_process.execSync(path.resolve(".\\resources\\tools\\pngout.exe")+" -l" + ' "' + this.pngFile + '"') 308 | try 309 | { 310 | var outInfo = child_process.execSync('"' + path.resolve(".\\resources\\tools\\pngout.exe") + '"' + " -l " + '"' + this.pngFile + '"') 311 | } catch (e) 312 | { 313 | console.error(e) 314 | return null 315 | } 316 | 317 | var c = /c./.exec(String(outInfo)); 318 | if (c != null) 319 | { 320 | switch (c[0]) 321 | { 322 | case "c0": 323 | return {code: 0, color: "Gray"}; 324 | case "c1": 325 | return {code: 2, color: "RGB"}; 326 | case "c3": 327 | return {code: 3, color: "pal"}; 328 | case "c4": 329 | return {code: 4, color: "Gray+Alpha"}; 330 | case "c6": 331 | return {code: 6, color: "RGB+Alpha"}; 332 | } 333 | } 334 | return null; 335 | } 336 | 337 | 338 | // 定义 PNG 处理器: 339 | 340 | 341 | var PROCE = {}; 342 | window.PROCE = PROCE; 343 | 344 | 345 | //--- truePNG ------------------------------------- 346 | PROCE.truePNG = function (opIndex, isLossy) 347 | { 348 | var re = {}; 349 | if (isLossy) 350 | { 351 | re.op = PROCE.truePNG.lossyOps[opIndex].op; 352 | } 353 | else 354 | { 355 | re.op = PROCE.truePNG.lossLessOps[opIndex].op; 356 | } 357 | 358 | 359 | re.cmd = function (in_path) 360 | { 361 | // console.log(this) 362 | return ' "' + in_path + '" ' + re.op; 363 | }; 364 | 365 | re.exeName = "TruePNG.exe"; 366 | return re; 367 | } 368 | 369 | PROCE.truePNG.lossLessOps = [ 370 | {op: "-f0,5 -i0 -g0 -md remove all -zc8 -zm8 -zw7 -zs0,1 -quiet -force -y", name: "指定最佳"}, 371 | {op: "-o max -quiet", name: "自动最佳"} 372 | ]; 373 | 374 | PROCE.truePNG.lossyOps = [ 375 | {op: "-f0,5 -i0 -g0 -md remove all -zc8 -zm8 -zw7 -zs0,1 -quiet -force -y", name: ""}, 376 | ]; 377 | 378 | 379 | //--- pngout ------------------------------------- 380 | PROCE.pngout = function (opIndex, isLossy) 381 | { 382 | var re = {}; 383 | if (isLossy) 384 | { 385 | re.op = PROCE.pngout.lossyOps[opIndex].op; 386 | } 387 | else 388 | { 389 | re.op = PROCE.pngout.lossLessOps[opIndex].op; 390 | } 391 | 392 | 393 | re.cmd = function (in_path) 394 | { 395 | // console.log(this) 396 | return ' "' + in_path + '" ' + re.op; 397 | }; 398 | 399 | re.exeName = "pngout.exe"; 400 | return re; 401 | } 402 | 403 | PROCE.pngout.lossLessOps = [ 404 | {op: "-f5 -s0 -k0 -q -y -force", name: "RGBA 最佳"}, 405 | {op: "-f0 -s0 -k0 -q -y -force", name: "pal 最佳"}, 406 | {op: "", name: "自动最佳"} 407 | ]; 408 | 409 | PROCE.pngout.lossyOps = [{op: "-c3 -quiet", name: "减色"}]; 410 | 411 | 412 | //--- cryopng ------------------------------------- 413 | PROCE.cryopng = function (opIndex, isLossy) 414 | { 415 | var re = {}; 416 | if (isLossy) 417 | { 418 | re.op = PROCE.cryopng.lossyOps[opIndex].op; 419 | } 420 | else 421 | { 422 | re.op = PROCE.cryopng.lossLessOps[opIndex].op; 423 | } 424 | 425 | re.cmd = function (in_path) 426 | { 427 | // console.log(this) 428 | return ' "' + in_path + '" ' + re.op; 429 | }; 430 | 431 | re.exeName = "cryopng.exe"; 432 | return re; 433 | } 434 | 435 | PROCE.cryopng.lossLessOps = [ 436 | {op: "-f1 -force -quiet -i0 -zc1 -zm1 -zs3 -zw32k -nx ", name: "RGBA 最佳"}, 437 | {op: "-o7 -force ", name: "自动最佳"}, 438 | {op: "", name: "自动"} 439 | ]; 440 | 441 | PROCE.cryopng.lossyOps = []; 442 | 443 | //--- pngwolf ------------------------------------- 444 | PROCE.pngwolf = function (opIndex, isLossy) 445 | { 446 | var re = {}; 447 | if (isLossy) 448 | { 449 | re.op = PROCE.pngwolf.lossyOps[opIndex].op; 450 | } 451 | else 452 | { 453 | re.op = PROCE.pngwolf.lossLessOps[opIndex].op; 454 | } 455 | 456 | re.cmd = function (in_path) 457 | { 458 | // console.log(this) 459 | return '--in="' + in_path + '" ' + '--out="' + in_path + '" ' + re.op; 460 | 461 | 462 | }; 463 | 464 | re.exeName = "pngwolf.exe"; 465 | return re; 466 | } 467 | 468 | PROCE.pngwolf.lossLessOps = [ 469 | { 470 | op: "--zlib-level=8 --zlib-strategy=1 --exclude-original --exclude-singles --exclude-heuristic --max-evaluations=1", 471 | name: "RGB 最佳" 472 | }, 473 | {op: "", name: "自动最佳"}, 474 | ]; 475 | 476 | PROCE.pngwolf.lossyOps = []; 477 | 478 | //--- zopflipng ------------------------------------- 479 | PROCE.zopflipng = function (opIndex, isLossy) 480 | { 481 | var re = {}; 482 | if (isLossy) 483 | { 484 | re.op = PROCE.zopflipng.lossyOps[opIndex].op; 485 | } 486 | else 487 | { 488 | re.op = PROCE.zopflipng.lossLessOps[opIndex].op; 489 | } 490 | 491 | re.cmd = function (in_path) 492 | { 493 | // console.log(this) 494 | return re.op + ' "' + in_path + '" ' + ' "' + in_path + '" '; 495 | }; 496 | 497 | re.exeName = "zopflipng.exe"; 498 | return re; 499 | } 500 | 501 | PROCE.zopflipng.lossLessOps = [ 502 | {op: "-y --iterations=35 --filters=01234mepb", name: "多次重最佳"}, 503 | {op: "-m -y", name: "自动最佳"}, 504 | ]; 505 | 506 | PROCE.zopflipng.lossyOps = []; 507 | 508 | 509 | //--- pngquant ------------------------------------- 510 | PROCE.pngquant = function (opIndex, isLossy) 511 | { 512 | var re = {}; 513 | if (isLossy) 514 | { 515 | re.op = PROCE.pngquant.lossyOps[opIndex].op; 516 | } 517 | else 518 | { 519 | re.op = PROCE.pngquant.lossLessOps[opIndex].op; 520 | } 521 | 522 | re.cmd = function (in_path) 523 | { 524 | // console.log(this) 525 | return '-o "' + in_path + ' " ' + re.op + ' "' + in_path + '"'; 526 | }; 527 | 528 | re.exeName = "pngquant.exe"; 529 | return re; 530 | } 531 | 532 | PROCE.pngquant.lossLessOps = []; 533 | 534 | PROCE.pngquant.lossyOps = [ 535 | {op: " --force --speed 1 --quality 0-1 ", name: "Q0"}, // 0 536 | {op: " --force --speed 1 --quality 0-10 ", name: "Q10"}, // 1 537 | {op: " --force --speed 1 --quality 20-30 ", name: "Q30"}, // 2 538 | {op: " --force --speed 1 --quality 10-20 ", name: "Q20"}, // 3 539 | {op: " --force --speed 1 --quality 30-40 ", name: "Q40"}, // 4 540 | {op: " --force --speed 1 --quality 40-50 ", name: "Q50"}, // 5 541 | {op: " --force --speed 1 --quality 50-60 ", name: "Q60"}, // 6 542 | {op: " --force --speed 1 --quality 60-70 ", name: "Q70"}, // 7 543 | {op: " --force --speed 1 --quality 70-80 ", name: "Q80"}, // 8 544 | {op: " --force --speed 1 --quality 80-90 ", name: "Q90"}, // 9 545 | {op: " --force --speed 1 --quality 90-100 ", name: "Q100"}, // 10 546 | {op: " --force --speed 1 --quality 0-1 256", name: "256-Q0"}, // 11 547 | {op: " --force --speed 1 --quality 0-10 256", name: "256-Q10"}, // 12 548 | {op: " --force --speed 1 --quality 20-30 256", name: "256-Q30"}, // 13 549 | {op: " --force --speed 1 --quality 10-20 256", name: "256-Q20"}, // 14 550 | {op: " --force --speed 1 --quality 30-40 256", name: "256-Q40"}, // 15 551 | {op: " --force --speed 1 --quality 40-50 256", name: "256-Q50"}, // 16 552 | {op: " --force --speed 1 --quality 50-60 256", name: "256-Q60"}, // 17 553 | {op: " --force --speed 1 --quality 60-70 256", name: "256-Q70"}, // 18 554 | {op: " --force --speed 1 --quality 70-80 256", name: "256-Q80"}, // 19 555 | {op: " --force --speed 1 --quality 80-90 256", name: "256-Q90"}, // 20 556 | {op: " --force --speed 1 --quality 90-100 256", name: "256-Q100"}, // 21 557 | {op: " --force --speed 1 256", name: "256-high"}, // 22 558 | {op: " --force --speed 1 --quality 40-80 256", name: "256-middle"}, // 23 559 | {op: " --force --speed 1 --quality 0-40 256", name: "256-low"}, // 24 560 | {op: " --force --speed 1 --quality 0-1 --posterize 4 256", name: "256-lowest"}, // 25 561 | {op: " --force --speed 1 ", name: "lossy-high"}, // 26 562 | {op: " --force --speed 1 --quality 0-40", name: "lossy-low"}, // 27 563 | 564 | 565 | ]; 566 | 567 | 568 | //--- ect ------------------------------------- 569 | PROCE.ect = function (opIndex, isLossy) 570 | { 571 | var re = {}; 572 | if (isLossy) 573 | { 574 | re.op = PROCE.ect.lossyOps[opIndex].op; 575 | } 576 | else 577 | { 578 | re.op = PROCE.ect.lossLessOps[opIndex].op; 579 | } 580 | 581 | re.cmd = function (in_path) 582 | { 583 | // console.log(this) 584 | return re.op + ' "' + in_path + '" ' 585 | }; 586 | 587 | re.exeName = "ect.exe"; 588 | return re; 589 | } 590 | 591 | PROCE.ect.lossLessOps = [ 592 | {op: "-s3", name: "最好"}, 593 | {op: "-s2", name: "平衡"}, 594 | {op: "-s1", name: "快速"}, 595 | ]; 596 | 597 | PROCE.ect.lossyOps = []; 598 | 599 | 600 | // window.l = new LimitPNG("../test/1.png")//debug 601 | 602 | function fileExists(in_path) 603 | { 604 | try 605 | { 606 | fs.accessSync(in_path, fs.R_OK | fs.W_OK) 607 | } catch (e) 608 | { 609 | console.error(e); 610 | return false 611 | } 612 | return true; 613 | } 614 | 615 | 616 | window.getFileSize = function (in_path) 617 | { 618 | try 619 | { 620 | var stat = fs.statSync(in_path) 621 | } catch (e) 622 | { 623 | console.error(e); 624 | return null 625 | } 626 | 627 | return stat.size; 628 | 629 | } 630 | 631 | 632 | ///------------ 633 | 634 | 635 | // cmds: { 636 | // //todo:还未完成所有参数: 637 | // delta_filters: { 638 | // name: "delta filters", 639 | // cmd: [ 640 | // {op: "f0", name: "None", miltiSelect: "f"}, 641 | // {op: "f1", name: "Sub", miltiSelect: "f"}, 642 | // {op: "f2", name: "Up", miltiSelect: "f"}, 643 | // {op: "f3", name: "Average", miltiSelect: "f"}, 644 | // {op: "f4", name: "Paeth", miltiSelect: "f"}, 645 | // {op: "f5", name: "Mixed", miltiSelect: "f"}, 646 | // {op: "fe", name: "combination for smallest size"}, 647 | // ] 648 | // }, 649 | // } 650 | 651 | 652 | -------------------------------------------------------------------------------- /move.bat: -------------------------------------------------------------------------------- 1 | copy /y app.asar release\limitPNG_64_beta5\resources\ 2 | copy /y app.asar release\limitPNG_32_beta5\resources\ 3 | pause -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "limit_png", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "electron bin" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "babel-plugin-transform-runtime": "^6.9.0", 13 | "babel-preset-es2015": "^6.9.0", 14 | "babel-preset-stage-3": "^6.5.0", 15 | "file-loader": "^0.8.5", 16 | "url-loader": "^0.5.7", 17 | "vue-hot-reload-api": "^1.3.2", 18 | "vue-html-loader": "^1.2.2", 19 | "vue-loader": "^8.5.2", 20 | "vue-style-loader": "^1.0.0", 21 | "webpack": "^1.13.1" 22 | }, 23 | "dependencies": { 24 | "dragula": "^3.7.1", 25 | "fs-extra": "^0.30.0", 26 | "vue": "^1.0.24", 27 | "vue-animated-list": "^1.0.2", 28 | "vuex": "^0.6.3" 29 | } 30 | } -------------------------------------------------------------------------------- /report.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by bgllj on 2016/6/8. 3 | */ 4 | 5 | 6 | 7 | window.getLog = function () 8 | { 9 | let log = ` 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | `; 21 | 22 | let zs0 = v.list_size_old; 23 | let zs1 = v.list_size_new; 24 | let ts = 0; 25 | for (let i = 0; i < v.list.length; i++) 26 | { 27 | let z0 = v.list[i].size_old; 28 | let z1 = v.list[i].size_new; 29 | let t = (v.list[i].time_consum / 1000).toFixed(1); 30 | ts = +ts + +t; 31 | 32 | log = log + ` 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | \n` 44 | } 45 | 46 | log = log + ` 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | \n` 58 | 59 | log = log + "
文件名路径原大小新大小模式减少大小减少比例耗时处理日志错误
${v.list[i].name}${v.list[i].path}${filter_file_size(z0)} (${z0} B)${filter_file_size(z1)} (${z1} B) [${v.mode}] ${filter_file_size(z1 - z0)}(${z1 - z0} B)${filter_file_size_pre(z1 / z0)}${t} s${v.list[i].log}${v.list[i].error_info}
总计 ${v.list.length }个文件${filter_file_size(zs0)} (${zs0} B)${filter_file_size(zs1)} (${zs1} B)线程数:${v.thread} ${filter_file_size(zs1 - zs0)}(${zs1 - zs0} B)${filter_file_size_pre(zs1 / zs0)}累计:${ts} s实际耗时: ${(v.time / 1000).toFixed(1)} s, 平均耗时:${(v.time / 1000 / v.list.length).toFixed(1)}s
"; 60 | 61 | 62 | return log; 63 | } 64 | 65 | 66 | window.outLogHtml = function () 67 | { 68 | let log = getLog(); 69 | let temp = os.tmpdir(); //"C:\Users\nullice\AppData\Local\Temp" 70 | temp = path.join(temp, "limitPNG_temp", "Report-limitPNG.html"); 71 | 72 | 73 | let html = ` 74 | 75 | 76 | 77 | 78 | limitPNG 处理报告 79 | 80 | 81 | 82 | 117 | ${log} 118 | 119 | 120 | `; 121 | fs.writeFileSync(temp, html, 'utf8'); 122 | shell.openExternal(temp); 123 | 124 | return html 125 | } 126 | 127 | 128 | var _out_log_try_z = 0; 129 | window.out_log_try = function () 130 | { 131 | _out_log_try_z++; 132 | if (_out_log_try_z >= 2) 133 | { 134 | outLogHtml(); 135 | _out_log_try_z = 0; 136 | } else 137 | { 138 | 139 | _out_log_try_z + 1; 140 | } 141 | 142 | } 143 | 144 | window.fileCompare = function () 145 | { 146 | function sortList(list, sortBy) 147 | { 148 | return list.sort(function (a, b) 149 | { 150 | return a[sortBy] - b[sortBy]; 151 | }); 152 | } 153 | 154 | dialog.showOpenDialog( 155 | {title: "对比文件大小 - 第一个选中的被认为是原图", properties: ["openFile", "multiSelections"]}, 156 | (files)=> 157 | { 158 | if (files != undefined && files.length > 0) 159 | { 160 | 161 | var log = ` 162 | 163 | 164 | 165 | 166 | 167 | `; 168 | 169 | var orgSize = 0; 170 | 171 | var ranks = []; 172 | 173 | for (let i = 0; i < files.length; i++) 174 | { 175 | let stat = fs.statSync(files[i]) 176 | let z0 = stat.size; 177 | if (i == 0) 178 | { 179 | orgSize = z0; 180 | log = log + ` 181 | 182 | 183 | 184 | 185 | 186 | \n` 187 | } else 188 | { 189 | ranks.push( { 190 | size: z0, html: ` 191 | 192 | 193 | 194 | 195 | \n` 196 | }) 197 | } 198 | } 199 | console.log(ranks); 200 | sortList(ranks, "size"); 201 | console.log(ranks); 202 | for (let i = 0; i < ranks.length; i++) 203 | { 204 | if(i==0) 205 | { 206 | log = log + ` ` 207 | }else 208 | { 209 | log = log + ` ` 210 | } 211 | 212 | log = log + ranks[i].html; 213 | } 214 | 215 | log = log + "
排名文件名大小减少大小减少比例
原图${path.basename(files[i])}${filter_file_size(z0)} (${z0} B)
${path.basename(files[i])}${filter_file_size(z0)} (${z0} B)${filter_file_size(orgSize - z0)} (${orgSize - z0} B)${filter_file_size_pre(z0 / orgSize)}
${i + 1}
${i + 1}
"; 216 | if(ranks.length>2) 217 | { 218 | log = log + `
第一名比第二名减少 ${filter_file_size(ranks[1].size - ranks[0].size)}(${ranks[1].size - ranks[0].size} B), 减少比例:${filter_file_size_pre(ranks[0].size / ranks[1].size)}
` 219 | 220 | } 221 | 222 | 223 | //-----out 224 | let temp = os.tmpdir(); //"C:\Users\nullice\AppData\Local\Temp" 225 | temp = path.join(temp, "limitPNG_temp", "Compare-Report-limitPNG.html"); 226 | 227 | 228 | let html = ` 229 | 230 | 231 | 232 | 233 | limitPNG 文件比较 234 | 235 | 236 | 237 | 290 | ${log} 291 | 292 | 293 | `; 294 | fs.writeFileSync(temp, html, 'utf8'); 295 | shell.openExternal(temp); 296 | 297 | return html 298 | 299 | 300 | } 301 | } 302 | ) 303 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by bgllj on 2016/5/25. 3 | */ 4 | const webpack = require('webpack'); 5 | 6 | module.exports = { 7 | entry: { 8 | main:'./index.js', 9 | vendor:['vue'] 10 | }, 11 | output: { 12 | path: './bin', 13 | filename: 'main.js' 14 | }, 15 | target: 'electron-renderer', 16 | 17 | module:{ 18 | loaders:[ 19 | {test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', query: {presets: 'es2015',}}, 20 | {test: /\.css$/, loader: 'style-loader!css-loader' }, 21 | {test: /\.sass$/, loaders: ["style", "css", "sass"]}, 22 | {test: /\.scss$/, loaders: ["style", "css", "sass"]}, 23 | {test: /\.(png|jpg|jpeg)$/, loader: 'url?limit=8000&name=../bin/img/[name].[ext]'}, 24 | {test: /\.vue$/, loader: 'vue'}, 25 | 26 | ]}, 27 | plugins: [ 28 | new webpack.BannerPlugin("---------nullice--------Banner 注释"), 29 | new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.bundle.js'), 30 | new webpack.optimize.UglifyJsPlugin({compress: {warnings: false}}) 31 | ], 32 | // devtool: 'eval-source-map' 33 | }; -------------------------------------------------------------------------------- /website/ICON.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/ICON.ico -------------------------------------------------------------------------------- /website/assets/css/admin.css: -------------------------------------------------------------------------------- 1 | /** 2 | * admin.css 3 | */ 4 | 5 | 6 | /* 7 | fixed-layout 固定头部和边栏布局 8 | */ 9 | 10 | html, 11 | body { 12 | height: 100%; 13 | overflow: hidden; 14 | } 15 | 16 | ul { 17 | margin-top: 0; 18 | } 19 | 20 | .admin-icon-yellow { 21 | color: #ffbe40; 22 | } 23 | 24 | .admin-header { 25 | position: fixed; 26 | top: 0; 27 | left: 0; 28 | right: 0; 29 | z-index: 1500; 30 | font-size: 1.4rem; 31 | margin-bottom: 0; 32 | } 33 | 34 | .admin-header-list a:hover :after { 35 | content: none; 36 | } 37 | 38 | .admin-main { 39 | position: relative; 40 | height: 100%; 41 | padding-top: 51px; 42 | background: #f3f3f3; 43 | } 44 | 45 | .admin-menu { 46 | position: fixed; 47 | z-index: 10; 48 | bottom: 30px; 49 | right: 20px; 50 | } 51 | 52 | .admin-sidebar { 53 | width: 260px; 54 | min-height: 100%; 55 | float: left; 56 | border-right: 1px solid #cecece; 57 | } 58 | 59 | .admin-sidebar.am-active { 60 | z-index: 1600; 61 | } 62 | 63 | .admin-sidebar-list { 64 | margin-bottom: 0; 65 | } 66 | 67 | .admin-sidebar-list li a { 68 | color: #5c5c5c; 69 | padding-left: 24px; 70 | } 71 | 72 | .admin-sidebar-list li:first-child { 73 | border-top: none; 74 | } 75 | 76 | .admin-sidebar-sub { 77 | margin-top: 0; 78 | margin-bottom: 0; 79 | box-shadow: 0 16px 8px -15px #e2e2e2 inset; 80 | background: #ececec; 81 | padding-left: 24px; 82 | } 83 | 84 | .admin-sidebar-sub li:first-child { 85 | border-top: 1px solid #dedede; 86 | } 87 | 88 | .admin-sidebar-panel { 89 | margin: 10px; 90 | } 91 | 92 | .admin-content { 93 | display: -webkit-box; 94 | display: -webkit-flex; 95 | display: -ms-flexbox; 96 | display: flex; 97 | -webkit-box-orient: vertical; 98 | -webkit-box-direction: normal; 99 | -webkit-flex-direction: column; 100 | -ms-flex-direction: column; 101 | flex-direction: column; 102 | background: #fff; 103 | } 104 | 105 | .admin-content, 106 | .admin-sidebar { 107 | height: 100%; 108 | overflow-x: hidden; 109 | overflow-y: scroll; 110 | -webkit-overflow-scrolling: touch; 111 | } 112 | 113 | .admin-content-body { 114 | -webkit-box-flex: 1; 115 | -webkit-flex: 1 0 auto; 116 | -ms-flex: 1 0 auto; 117 | flex: 1 0 auto; 118 | } 119 | 120 | .admin-content-footer { 121 | font-size: 85%; 122 | color: #777; 123 | } 124 | 125 | .admin-content-list { 126 | border: 1px solid #e9ecf1; 127 | margin-top: 0; 128 | } 129 | 130 | .admin-content-list li { 131 | border: 1px solid #e9ecf1; 132 | border-width: 0 1px; 133 | margin-left: -1px; 134 | } 135 | 136 | .admin-content-list li:first-child { 137 | border-left: none; 138 | } 139 | 140 | .admin-content-list li:last-child { 141 | border-right: none; 142 | } 143 | 144 | .admin-content-table a { 145 | color: #535353; 146 | } 147 | .admin-content-file { 148 | margin-bottom: 0; 149 | color: #666; 150 | } 151 | 152 | .admin-content-file p { 153 | margin: 0 0 5px 0; 154 | font-size: 1.4rem; 155 | } 156 | 157 | .admin-content-file li { 158 | padding: 10px 0; 159 | } 160 | 161 | .admin-content-file li:first-child { 162 | border-top: none; 163 | } 164 | 165 | .admin-content-file li:last-child { 166 | border-bottom: none; 167 | } 168 | 169 | .admin-content-file li .am-progress { 170 | margin-bottom: 4px; 171 | } 172 | 173 | .admin-content-file li .am-progress-bar { 174 | line-height: 14px; 175 | } 176 | 177 | .admin-content-task { 178 | margin-bottom: 0; 179 | } 180 | 181 | .admin-content-task li { 182 | padding: 5px 0; 183 | border-color: #eee; 184 | } 185 | 186 | .admin-content-task li:first-child { 187 | border-top: none; 188 | } 189 | 190 | .admin-content-task li:last-child { 191 | border-bottom: none; 192 | } 193 | 194 | .admin-task-meta { 195 | font-size: 1.2rem; 196 | color: #999; 197 | } 198 | 199 | .admin-task-bd { 200 | font-size: 1.4rem; 201 | margin-bottom: 5px; 202 | } 203 | 204 | .admin-content-comment { 205 | margin-bottom: 0; 206 | } 207 | 208 | .admin-content-comment .am-comment-bd { 209 | font-size: 1.4rem; 210 | } 211 | 212 | .admin-content-pagination { 213 | margin-bottom: 0; 214 | } 215 | .admin-content-pagination li a { 216 | padding: 4px 8px; 217 | } 218 | 219 | @media only screen and (min-width: 641px) { 220 | .admin-sidebar { 221 | display: block; 222 | position: static; 223 | background: none; 224 | } 225 | 226 | .admin-offcanvas-bar { 227 | position: static; 228 | width: auto; 229 | background: none; 230 | -webkit-transform: translate3d(0, 0, 0); 231 | -ms-transform: translate3d(0, 0, 0); 232 | transform: translate3d(0, 0, 0); 233 | } 234 | .admin-offcanvas-bar:after { 235 | content: none; 236 | } 237 | } 238 | 239 | @media only screen and (max-width: 640px) { 240 | .admin-sidebar { 241 | width: inherit; 242 | } 243 | 244 | .admin-offcanvas-bar { 245 | background: #f3f3f3; 246 | } 247 | 248 | .admin-offcanvas-bar:after { 249 | background: #BABABA; 250 | } 251 | 252 | .admin-sidebar-list a:hover, .admin-sidebar-list a:active{ 253 | -webkit-transition: background-color .3s ease; 254 | -moz-transition: background-color .3s ease; 255 | -ms-transition: background-color .3s ease; 256 | -o-transition: background-color .3s ease; 257 | transition: background-color .3s ease; 258 | background: #E4E4E4; 259 | } 260 | 261 | .admin-content-list li { 262 | padding: 10px; 263 | border-width: 1px 0; 264 | margin-top: -1px; 265 | } 266 | 267 | .admin-content-list li:first-child { 268 | border-top: none; 269 | } 270 | 271 | .admin-content-list li:last-child { 272 | border-bottom: none; 273 | } 274 | 275 | .admin-form-text { 276 | text-align: left !important; 277 | } 278 | 279 | } 280 | 281 | /* 282 | * user.html css 283 | */ 284 | .user-info { 285 | margin-bottom: 15px; 286 | } 287 | 288 | .user-info .am-progress { 289 | margin-bottom: 4px; 290 | } 291 | 292 | .user-info p { 293 | margin: 5px; 294 | } 295 | 296 | .user-info-order { 297 | font-size: 1.4rem; 298 | } 299 | 300 | /* 301 | * errorLog.html css 302 | */ 303 | 304 | .error-log .am-pre-scrollable { 305 | max-height: 40rem; 306 | } 307 | 308 | /* 309 | * table.html css 310 | */ 311 | 312 | .table-main { 313 | font-size: 1.4rem; 314 | padding: .5rem; 315 | } 316 | 317 | .table-main button { 318 | background: #fff; 319 | } 320 | 321 | .table-check { 322 | width: 30px; 323 | } 324 | 325 | .table-id { 326 | width: 50px; 327 | } 328 | 329 | @media only screen and (max-width: 640px) { 330 | .table-select { 331 | margin-top: 10px; 332 | margin-left: 5px; 333 | } 334 | } 335 | 336 | /* 337 | gallery.html css 338 | */ 339 | 340 | .gallery-list li { 341 | padding: 10px; 342 | } 343 | 344 | .gallery-list a { 345 | color: #666; 346 | } 347 | 348 | .gallery-list a:hover { 349 | color: #3bb4f2; 350 | } 351 | 352 | .gallery-title { 353 | margin-top: 6px; 354 | font-size: 1.4rem; 355 | } 356 | 357 | .gallery-desc { 358 | font-size: 1.2rem; 359 | margin-top: 4px; 360 | } 361 | 362 | /* 363 | 404.html css 364 | */ 365 | 366 | .page-404 { 367 | background: #fff; 368 | border: none; 369 | width: 200px; 370 | margin: 0 auto; 371 | } 372 | -------------------------------------------------------------------------------- /website/assets/css/app.css: -------------------------------------------------------------------------------- 1 | /* Write your styles */ -------------------------------------------------------------------------------- /website/assets/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /website/assets/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /website/assets/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /website/assets/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /website/assets/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /website/assets/i/app-icon72x72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/i/app-icon72x72@2x.png -------------------------------------------------------------------------------- /website/assets/i/examples/admin-chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/i/examples/admin-chrome.png -------------------------------------------------------------------------------- /website/assets/i/examples/admin-firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/i/examples/admin-firefox.png -------------------------------------------------------------------------------- /website/assets/i/examples/admin-ie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/i/examples/admin-ie.png -------------------------------------------------------------------------------- /website/assets/i/examples/admin-opera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/i/examples/admin-opera.png -------------------------------------------------------------------------------- /website/assets/i/examples/admin-safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/i/examples/admin-safari.png -------------------------------------------------------------------------------- /website/assets/i/examples/adminPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/i/examples/adminPage.png -------------------------------------------------------------------------------- /website/assets/i/examples/blogPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/i/examples/blogPage.png -------------------------------------------------------------------------------- /website/assets/i/examples/landing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/i/examples/landing.png -------------------------------------------------------------------------------- /website/assets/i/examples/landingPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/i/examples/landingPage.png -------------------------------------------------------------------------------- /website/assets/i/examples/loginPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/i/examples/loginPage.png -------------------------------------------------------------------------------- /website/assets/i/examples/sidebarPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/i/examples/sidebarPage.png -------------------------------------------------------------------------------- /website/assets/i/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/i/favicon.png -------------------------------------------------------------------------------- /website/assets/i/startup-640x1096.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/i/startup-640x1096.png -------------------------------------------------------------------------------- /website/assets/js/amazeui.widgets.helper.js: -------------------------------------------------------------------------------- 1 | /*! Amaze UI v2.7.0 ~ Handlebars helper | by Amaze UI Team | (c) 2016 AllMobilize, Inc. | Licensed under MIT | 2016-05-24T10:02:50+0800 */ 2 | (function(undefined) { 3 | 'use strict'; 4 | 5 | var registerIfCondHelper = function(hbs) { 6 | hbs.registerHelper('ifCond', function(v1, operator, v2, options) { 7 | switch (operator) { 8 | case '==': 9 | return (v1 == v2) ? options.fn(this) : options.inverse(this); 10 | break; 11 | case '===': 12 | return (v1 === v2) ? options.fn(this) : options.inverse(this); 13 | break; 14 | case '<': 15 | return (v1 < v2) ? options.fn(this) : options.inverse(this); 16 | break; 17 | case '<=': 18 | return (v1 <= v2) ? options.fn(this) : options.inverse(this); 19 | break; 20 | case '>': 21 | return (v1 > v2) ? options.fn(this) : options.inverse(this); 22 | break; 23 | case '>=': 24 | return (v1 >= v2) ? options.fn(this) : options.inverse(this); 25 | break; 26 | default: 27 | return options.inverse(this); 28 | break; 29 | } 30 | return options.inverse(this); 31 | }); 32 | }; 33 | 34 | if (typeof module !== 'undefined' && module.exports) { 35 | module.exports = registerIfCondHelper; 36 | } 37 | 38 | this.Handlebars && registerIfCondHelper(this.Handlebars); 39 | }).call(this); 40 | 41 | (function(undefined){ 42 | 'use strict'; 43 | 44 | var registerAMUIPartials = function(hbs) { 45 | hbs.registerPartial('accordion', "{{#this}}\n
\n {{#each content}}\n
\n
\n {{{title}}}\n
\n
\n \n
\n {{{content}}}\n
\n
\n
\n {{/each}}\n
\n{{/this}}\n"); 46 | 47 | hbs.registerPartial('divider', "{{#this}}\n
\n{{/this}}\n"); 48 | 49 | hbs.registerPartial('duoshuo', "{{#this}}\n
\n
\n
\n
\n{{/this}}"); 50 | 51 | hbs.registerPartial('figure', "{{#this}}\n
\n {{#if content.link}}{{/if}}\n\n {{#if options.figcaptionPosition}}\n {{#ifCond options.figcaptionPosition '==' 'top'}}\n {{#if content.figcaption}}\n
\n {{content.figcaption}}\n
\n {{/if}}\n {{/ifCond}}\n {{/if}}\n\n {{#if content.img}}\n \"{{#if\n {{/if}}\n {{#if options.figcaptionPosition}}\n {{#ifCond options.figcaptionPosition '==' 'bottom'}}\n {{#if content.figcaption}}\n
\n {{content.figcaption}}\n
\n {{/if}}\n {{/ifCond}}\n {{else}}\n {{#if content.figcaption}}\n
\n {{content.figcaption}}\n
\n {{/if}}\n {{/if}}\n\n {{#if content.link}}
{{/if}}\n
\n{{/this}}\n"); 52 | 53 | hbs.registerPartial('footer', "{{#this}}\n \n\n \n{{/this}}\n"); 54 | 55 | hbs.registerPartial('gallery', "{{#this}}\n \n{{/this}}\n"); 56 | 57 | hbs.registerPartial('gotop', "{{#this}}\n
\n \n {{#if content.title}}\n {{content.title}}\n {{/if}}\n {{#if content.customIcon}}\n \n {{else}}\n {{#if content.icon}}\n \n {{else}}\n \n {{/if}}\n {{/if}}\n \n
\n{{/this}}\n"); 58 | 59 | hbs.registerPartial('header', "{{#this}}\n
\n {{#if content.left}}\n
\n {{#each content.left}}\n \n {{#if title}}\n \n {{title}}\n \n {{/if}}\n\n {{# if customIcon}}\n \"\"/\n {{else}}\n {{#if icon}}\n \n {{/if}}\n {{/if}}\n \n {{/each}}\n
\n {{/if}}\n\n {{#if content.title}}\n

\n {{#if content.link}}\n \n {{{content.title}}}\n \n {{else}}\n {{{content.title}}}\n {{/if}}\n

\n {{/if}}\n\n {{#if content.right}}\n
\n {{#each content.right}}\n \n {{#if title}}\n \n {{title}}\n \n {{/if}}\n\n {{# if customIcon}}\n \"\"/\n {{else}}\n {{#if icon}}\n \n {{/if}}\n {{/if}}\n \n {{/each}}\n
\n {{/if}}\n
\n{{/this}}\n"); 60 | 61 | hbs.registerPartial('intro', "{{#this }}\n
\n {{#if content.title}}\n
\n

{{{content.title}}}

\n {{#if content.more.link}}\n {{#ifCond options.position '==' 'top'}}\n {{content.more.title}}\n {{/ifCond}}\n {{/if}}\n
\n {{/if}}\n\n
\n {{#if content.left}}\n {{{content.left}}}
\n {{/if}}\n {{#if content.right}}\n {{{content.right}}}
\n {{/if}}\n \n {{#ifCond options.position '==' 'bottom'}}\n
\n {{content.more.title}}\n
\n {{/ifCond}}\n \n{{/this}}\n"); 62 | 63 | hbs.registerPartial('list_news', "{{#this}}\n
\n \n {{#if content.header.title}}\n
\n {{#if content.header.link}} \n \n

{{{content.header.title}}}

\n {{#ifCond content.header.morePosition '==' 'top'}}\n {{{content.header.moreText}}}\n {{/ifCond}}\n
\n {{else}} \n

{{{content.header.title}}}

\n {{/if}}\n
\n {{/if}}\n\n
\n \n
\n\n {{#ifCond content.header.morePosition '==' 'bottom'}}\n {{#if content.header.link}}\n
\n {{{content.header.moreText}}}\n
\n {{/if}}\n {{/ifCond}}\n
\n{{/this}}\n"); 64 | 65 | hbs.registerPartial('map', "{{#this}}\n
\n
\n
\n{{/this}}"); 66 | 67 | hbs.registerPartial('mechat', "{{#this}}\n
\n
\n
\n{{/this}}"); 68 | 69 | hbs.registerPartial('menu', "{{#this}}\n \n{{/this}}\n"); 70 | 71 | hbs.registerPartial('navbar', "{{#this}}\n
\n {{#if content}}\n \n {{/if}}\n
\n{{/this}}\n"); 72 | 73 | hbs.registerPartial('pagination', "{{#this}}\n \n{{/this}}\n"); 74 | 75 | hbs.registerPartial('paragraph', "{{#this}}\n
\n\n {{#if content}}\n {{{ content.content }}}\n {{/if}}\n
\n{{/this}}\n"); 76 | 77 | hbs.registerPartial('slider', "{{#this}}\n
\n \n
\n{{/this}}"); 78 | 79 | hbs.registerPartial('tabs', "{{#this}}\n
\n {{#if content}}\n \n
\n {{#each content}}\n
\n {{{content}}}\n
\n {{/each}}\n
\n {{/if}}\n
\n{{/this}}\n"); 80 | 81 | hbs.registerPartial('titlebar', "{{#this}}\n
\n {{#if content.title}}\n

\n {{#if content.link}}\n {{{content.title}}}\n {{else}}\n {{{content.title}}}\n {{/if}}\n

\n {{/if}}\n\n {{#if content.nav}}\n \n {{/if}}\n
\n{{/this}}\n"); 82 | 83 | hbs.registerPartial('wechatpay', "{{#this}}\n
\n \n
\n{{/this}}\n"); 84 | 85 | }; 86 | 87 | if (typeof module !== 'undefined' && module.exports) { 88 | module.exports = registerAMUIPartials; 89 | } 90 | 91 | this.Handlebars && registerAMUIPartials(this.Handlebars); 92 | }).call(this); 93 | -------------------------------------------------------------------------------- /website/assets/js/amazeui.widgets.helper.min.js: -------------------------------------------------------------------------------- 1 | /*! Amaze UI v2.7.0 ~ Handlebars helper | by Amaze UI Team | (c) 2016 AllMobilize, Inc. | Licensed under MIT | 2016-05-24T10:02:50+0800 */ 2 | (function(i){"use strict";var n=function(i){i.registerHelper("ifCond",function(i,n,t,a){switch(n){case"==":return i==t?a.fn(this):a.inverse(this);case"===":return i===t?a.fn(this):a.inverse(this);case"<":return t>i?a.fn(this):a.inverse(this);case"<=":return t>=i?a.fn(this):a.inverse(this);case">":return i>t?a.fn(this):a.inverse(this);case">=":return i>=t?a.fn(this):a.inverse(this);default:return a.inverse(this)}return a.inverse(this)})};"undefined"!=typeof module&&module.exports&&(module.exports=n),this.Handlebars&&n(this.Handlebars)}).call(this),function(i){"use strict";var n=function(i){i.registerPartial("accordion",'{{#this}}\n
\n {{#each content}}\n
\n
\n {{{title}}}\n
\n
\n \n
\n {{{content}}}\n
\n
\n
\n {{/each}}\n
\n{{/this}}\n'),i.registerPartial("divider",'{{#this}}\n
\n{{/this}}\n'),i.registerPartial("duoshuo",'{{#this}}\n
\n
\n
\n
\n{{/this}}'),i.registerPartial("figure",'{{#this}}\n
\n {{#if content.link}}{{/if}}\n\n {{#if options.figcaptionPosition}}\n {{#ifCond options.figcaptionPosition \'==\' \'top\'}}\n {{#if content.figcaption}}\n
\n {{content.figcaption}}\n
\n {{/if}}\n {{/ifCond}}\n {{/if}}\n\n {{#if content.img}}\n {{#if content.imgAlt}}{{content.imgAlt}}{{else}}{{content.figcaption}}{{/if}}\n {{/if}}\n {{#if options.figcaptionPosition}}\n {{#ifCond options.figcaptionPosition \'==\' \'bottom\'}}\n {{#if content.figcaption}}\n
\n {{content.figcaption}}\n
\n {{/if}}\n {{/ifCond}}\n {{else}}\n {{#if content.figcaption}}\n
\n {{content.figcaption}}\n
\n {{/if}}\n {{/if}}\n\n {{#if content.link}}
{{/if}}\n
\n{{/this}}\n'),i.registerPartial("footer",'{{#this}}\n \n\n \n{{/this}}\n'),i.registerPartial("gallery",'{{#this}}\n \n{{/this}}\n'),i.registerPartial("gotop",'{{#this}}\n
\n \n {{#if content.title}}\n {{content.title}}\n {{/if}}\n {{#if content.customIcon}}\n \n {{else}}\n {{#if content.icon}}\n \n {{else}}\n \n {{/if}}\n {{/if}}\n \n
\n{{/this}}\n'),i.registerPartial("header",'{{#this}}\n
\n {{#if content.left}}\n
\n {{#each content.left}}\n \n {{#if title}}\n \n {{title}}\n \n {{/if}}\n\n {{# if customIcon}}\n \n {{else}}\n {{#if icon}}\n \n {{/if}}\n {{/if}}\n \n {{/each}}\n
\n {{/if}}\n\n {{#if content.title}}\n

\n {{#if content.link}}\n \n {{{content.title}}}\n \n {{else}}\n {{{content.title}}}\n {{/if}}\n

\n {{/if}}\n\n {{#if content.right}}\n
\n {{#each content.right}}\n \n {{#if title}}\n \n {{title}}\n \n {{/if}}\n\n {{# if customIcon}}\n \n {{else}}\n {{#if icon}}\n \n {{/if}}\n {{/if}}\n \n {{/each}}\n
\n {{/if}}\n
\n{{/this}}\n'),i.registerPartial("intro",'{{#this }}\n
\n {{#if content.title}}\n
\n

{{{content.title}}}

\n {{#if content.more.link}}\n {{#ifCond options.position \'==\' \'top\'}}\n {{content.more.title}}\n {{/ifCond}}\n {{/if}}\n
\n {{/if}}\n\n
\n {{#if content.left}}\n {{{content.left}}}
\n {{/if}}\n {{#if content.right}}\n {{{content.right}}}
\n {{/if}}\n \n {{#ifCond options.position \'==\' \'bottom\'}}\n
\n {{content.more.title}}\n
\n {{/ifCond}}\n \n{{/this}}\n'),i.registerPartial("list_news",'{{#this}}\n
\n \n {{#if content.header.title}}\n
\n {{#if content.header.link}} \n \n

{{{content.header.title}}}

\n {{#ifCond content.header.morePosition \'==\' \'top\'}}\n {{{content.header.moreText}}}\n {{/ifCond}}\n
\n {{else}} \n

{{{content.header.title}}}

\n {{/if}}\n
\n {{/if}}\n\n
\n \n
\n\n {{#ifCond content.header.morePosition \'==\' \'bottom\'}}\n {{#if content.header.link}}\n
\n {{{content.header.moreText}}}\n
\n {{/if}}\n {{/ifCond}}\n
\n{{/this}}\n'),i.registerPartial("map",'{{#this}}\n
\n
\n
\n{{/this}}'),i.registerPartial("mechat",'{{#this}}\n
\n
\n
\n{{/this}}'),i.registerPartial("menu",'{{#this}}\n \n{{/this}}\n'),i.registerPartial("navbar",'{{#this}}\n
\n {{#if content}}\n \n {{/if}}\n
\n{{/this}}\n'),i.registerPartial("pagination",'{{#this}}\n \n{{/this}}\n'),i.registerPartial("paragraph",'{{#this}}\n
\n\n {{#if content}}\n {{{ content.content }}}\n {{/if}}\n
\n{{/this}}\n'),i.registerPartial("slider",'{{#this}}\n
\n \n
\n{{/this}}'),i.registerPartial("tabs",'{{#this}}\n
\n {{#if content}}\n \n
\n {{#each content}}\n
\n {{{content}}}\n
\n {{/each}}\n
\n {{/if}}\n
\n{{/this}}\n'),i.registerPartial("titlebar",'{{#this}}\n
\n {{#if content.title}}\n

\n {{#if content.link}}\n {{{content.title}}}\n {{else}}\n {{{content.title}}}\n {{/if}}\n

\n {{/if}}\n\n {{#if content.nav}}\n \n {{/if}}\n
\n{{/this}}\n'), 3 | i.registerPartial("wechatpay",'{{#this}}\n
\n \n
\n{{/this}}\n')};"undefined"!=typeof module&&module.exports&&(module.exports=n),this.Handlebars&&n(this.Handlebars)}.call(this); -------------------------------------------------------------------------------- /website/assets/js/app.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 'use strict'; 3 | 4 | $(function() { 5 | var $fullText = $('.admin-fullText'); 6 | $('#admin-fullscreen').on('click', function() { 7 | $.AMUI.fullscreen.toggle(); 8 | }); 9 | 10 | $(document).on($.AMUI.fullscreen.raw.fullscreenchange, function() { 11 | $fullText.text($.AMUI.fullscreen.isFullscreen ? '退出全屏' : '开启全屏'); 12 | }); 13 | }); 14 | })(jQuery); 15 | -------------------------------------------------------------------------------- /website/css.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/css.css -------------------------------------------------------------------------------- /website/img/1_lim[lossy-low].png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/img/1_lim[lossy-low].png -------------------------------------------------------------------------------- /website/img/cpu.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/img/gif3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/img/gif3.gif -------------------------------------------------------------------------------- /website/img/logo-mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/img/logo-mini.png -------------------------------------------------------------------------------- /website/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/img/logo.png -------------------------------------------------------------------------------- /website/img/lossy.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/img/no1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/img/option.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/img/timecus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/img/timecus.png -------------------------------------------------------------------------------- /website/img/top.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/img/vs/PNG_lossy_vs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/img/vs/PNG_lossy_vs.png -------------------------------------------------------------------------------- /website/img/vs/download.url: -------------------------------------------------------------------------------- 1 | [InternetShortcut] 2 | URL=data:text/mce-internal,content,%3Cimg%20id%3D%22__wp-temp-img-id%22%20src%3D%22http%3A//77we48.com1.z0.glb.clouddn.com/PNG_%25E5%25AF%25B9%25E6%25AF%2594_%25E7%25BD%2591%25E9%25A1%25B5%2520banner_1.png%22%20width%3D%22900%22%20height%3D%22300%22%20/%3E 3 | -------------------------------------------------------------------------------- /website/img/vs/lossy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/img/vs/lossy.png -------------------------------------------------------------------------------- /website/img/vs/ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/img/vs/ss.png -------------------------------------------------------------------------------- /website/img/vs/vuelogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/img/vs/vuelogo.png -------------------------------------------------------------------------------- /website/index.css: -------------------------------------------------------------------------------- 1 | .get { 2 | background: #FFFFFF; 3 | color: #02BECA; 4 | text-align: center; 5 | padding: 40px 0; 6 | } 7 | 8 | .am-topbar { 9 | background: rgba(255, 255, 255, .23); 10 | border: none; 11 | color: #666; 12 | } 13 | 14 | .get-title { 15 | font-size: 200%; 16 | border: 2px solid #fff; 17 | padding: 10px; 18 | display: inline-block; 19 | margin: 0; 20 | } 21 | 22 | h1.get-title { 23 | font-weight: lighter; 24 | } 25 | 26 | h1.get-title+p { 27 | margin-top: 0; 28 | color: #90BBBB; 29 | font-weight: lighter; 30 | } 31 | 32 | .get-btn { 33 | background-color: #01ABC3; 34 | background: linear-gradient(70deg, #1ad7f9, #5eece4); 35 | border-radius: 100px; 36 | /* color: #fff; */ 37 | } 38 | 39 | 40 | .get-btn:hover { 41 | transition: all .3s ease; 42 | margin-top: -2px; 43 | background: linear-gradient(40deg, #20cdff, #2dfff3); 44 | box-shadow: 0 10px 20px #54f3ff; 45 | /* border-radius: 0px; */ 46 | } 47 | 48 | a.am-btn.am-btn-sm.get-btn { 49 | color: #fff; 50 | padding: 8px 40px; 51 | font-size: 15px; 52 | } 53 | 54 | .sur_info { 55 | font-size: 11px; 56 | color: #ADADAD; 57 | margin-top: -12px; 58 | } 59 | 60 | .detail { 61 | background: #fff; 62 | margin-top: 25px; 63 | } 64 | 65 | h2.detail-h2, .book-h2 { 66 | font-weight: 100; 67 | color: #004B50; 68 | text-align: left; 69 | margin-bottom: 10px; 70 | } 71 | 72 | .book-h2{ 73 | color: #fff; 74 | } 75 | 76 | h2 span { 77 | color: rgb(171, 175, 175); 78 | font-size: 17px; 79 | padding-left: 10px; 80 | } 81 | 82 | .hope p{ 83 | color: #fafbef; 84 | font-size: 14px; 85 | font-weight: lighter; 86 | 87 | } 88 | 89 | 90 | .book-h2 span{ 91 | color: rgb(236, 242, 242); 92 | font-size: 17px; 93 | padding-left: 10px; 94 | } 95 | 96 | .detail p { 97 | color: #797979; 98 | margin-top: 10px; 99 | } 100 | 101 | 102 | 103 | 104 | tr.org, tr.org:hover { 105 | background-color: #61D1D4; 106 | color: #FFF; 107 | } 108 | 109 | tr.cp_no1 { 110 | background-color: rgba(77, 233, 234, 0.1); 111 | color: #20A8B7; 112 | } 113 | 114 | th { 115 | color: #A7A7A7; 116 | font-weight: lighter; 117 | } 118 | 119 | td, th { 120 | padding: 6px 9px; 121 | } 122 | 123 | table { 124 | margin: 20px; 125 | } 126 | 127 | tr { 128 | color: #989898; 129 | } 130 | 131 | 132 | table { 133 | margin-left: auto; 134 | margin-right: auto; 135 | } 136 | 137 | 138 | .am-figure-default figcaption { 139 | color: #949494; 140 | font-weight: lighter; 141 | } 142 | 143 | 144 | 145 | .am-topbar { 146 | font-weight: 100; 147 | } 148 | 149 | .am-topbar-brand { 150 | 151 | font-weight: normal; 152 | } 153 | .detail-h3 { 154 | /* color: #1f8dd6; */ 155 | color: #004B50; 156 | font-weight: lighter; 157 | font-size: 17px; 158 | } 159 | 160 | 161 | ul.am-avg-sm-3.boxes { 162 | margin-top: 100px; 163 | } 164 | 165 | .about-title { 166 | font-weight: lighter; 167 | } 168 | 169 | 170 | .footer p { 171 | color: #7f8c8d; 172 | margin: 0; 173 | padding: 15px 0; 174 | text-align: center; 175 | background: #E0E0E0; 176 | } 177 | 178 | .detail.d2{ 179 | margin-top: 20px; 180 | } 181 | 182 | 183 | 184 | .about { 185 | margin-top: 150px; 186 | } 187 | 188 | .am-topbar-brand img { 189 | margin-bottom: 8px; 190 | } 191 | h2.about-title.about-color { 192 | font-size: 25px; 193 | padding-bottom: 0px; 194 | margin-bottom: 5px; 195 | } 196 | 197 | .about-color+p { 198 | padding-top: 0px; 199 | margin-top: 5px; 200 | text-align: center; 201 | } 202 | a { 203 | color: #33A6BB; 204 | } 205 | 206 | .update { 207 | font-size: 10px; 208 | margin-top: 5px; 209 | padding: 5px; 210 | color: #A2A1A0; 211 | } 212 | 213 | .sur_info a:hover { 214 | color: #3BB4F2; 215 | } 216 | .sur_info a { 217 | color: #AAADAD; 218 | } 219 | 220 | 221 | 222 | 223 | .changelog{ 224 | 225 | margin-top: 5px; 226 | display: none; 227 | } 228 | 229 | .update:hover .changelog{ 230 | 231 | display: block; 232 | } 233 | -------------------------------------------------------------------------------- /website/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | limitPNG | PNG 图片极限压缩工具 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 100 | 101 | 102 | 103 | 104 | 105 |
106 |
107 |

108 | 109 | limitPNG 110 |

111 | 112 | 115 | 116 |
117 | 126 |
127 |
128 |
129 | 130 | 131 | 132 |
133 |
134 |
135 |
136 | 137 |
138 | 139 |

如此简单的到达 PNG 无损压缩的极限

140 |

141 | 比其他同类压缩工具压缩的更小,支持无损压缩和有损压缩,可调多线程处理,丰富参数可选,并且这是免费的 142 |

143 |

144 | 下载 145 |

146 | 147 |
Windows 7/8/10 : beta4 (2016/6/12) 148 | 64 位 (37 MB) 149 | / 32 位 (31 MB) 150 | / 浓缩版 151 | / 网盘和历史版本 152 |
153 | 154 |
beta4 修复了无法清空列表的 bug;现在添加文件是追加到列表;启动速度提升; 155 |
beta3 修复了“无损-强力”模式无法正常使用和任务进行中可以拖入文件的 bug
156 |
157 | 158 |
如果需要压缩超大批量的图片,可以试一试能够保存压缩进度、不到 1.3 MB 的 gluttonyPNG 159 | 160 |
161 | 162 | 163 | 164 | 165 | 166 |
167 |
168 | 169 |
170 | 171 | 172 |
173 | 174 |
175 |
176 |

无损压缩 不改变任何像素的安全压缩

177 |

limitPNG 支持无损压缩和有损压缩两种压缩方式,其中无损压缩是不损失任何画质的压缩方法,与有损压缩相比(如 tinypng),虽然体积没优势,但是在对品质有要求的生产环境中不改变原图任何一个像素是必须的

178 | 179 |

而在无损压缩工具中,即使比起公认无损压缩率最高的工具: PNGGauntle 和 scriptPNG ,limitPNG 仍能压缩的比它们压缩的更小:

180 | 181 |

182 |

183 | 184 | 185 |
186 | 示例-网站图片 187 |
188 |
189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 |
排名文件名大小减少大小减少比例
原图网站图片-1.png11.19 KB (11460 B)
1网站图片-1 [limitPNG].png7.71 KB (7898 B)3.48 KB (3562 B)- 31%
2网站图片-1_[scriptPNG].png8.13 KB (8325 B)3.06 KB (3135 B)- 27%
3网站图片-1_[PNGGauntle].png8.13 KB (8325 B)3.06 KB (3135 B)- 27%
4网站图片-1_[Caesium].png14.96 KB (15319 B)-3859 B (-3859 B)+ 33%
236 | 237 |

238 | 239 |

240 |

241 | 242 | 243 |
244 | 示例-网站图片2 245 |
246 |
247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 |
排名文件名大小减少大小减少比例
原图网页 banner_1.png330.63 KB (338566 B)
1网页 banner_1_[limitPNG].png279.99 KB (286711 B)50.64 KB (51855 B)- 15%
2网页 banner_1_[PNGGauntle].png282.76 KB (289547 B)47.87 KB (49019 B)- 14%
3网页 banner_1_[scriptPNG].png287.16 KB (294053 B)43.47 KB (44513 B)- 13%
4网页 banner_1_[Caesium].png389.40 KB (398746 B)-60180 B (-60180 B)+ 17%
294 | 295 |

296 | 297 |

有损压缩 损失图片质量的暴力压缩

298 |

虽然有损压缩会损失画质,但是一些场合对画质要求并不高,而有损压缩能减少更多的体积。limitPNG 也支持有损压缩模式,并且相对于同类工具(tinypng、PP鸭、pngyu 等),limitPNG 有更多画质选项,可以更灵活的在画质和尺寸间取舍。 299 | 当然压缩效果也毫不逊色:

300 | 301 | 302 | 303 |

更多对比测试

304 |
305 |
306 |
307 | 308 | 309 |
310 |
311 |
312 | 313 |

压缩速度

314 |

315 | limitPNG 有“快速”、“强力”、“极限” 3 级压缩模式可选。极限压缩会花费大量时间,如果你没那个耐心可以选择“快速”模式。 316 | 快速模式通常已经能达到极限效果的 90%,而花费时间只要“极限”模式的 20 分之一甚至更少。 317 | 当然为了追求最后 10% 的压缩效果,多花一些时间也是值得的,而其中的取舍由你决定。 318 |

319 |
320 | 321 |
322 | 323 |
324 |
325 |
326 |
327 | 328 | 329 | 330 | 331 |
332 | 333 |
334 |
335 |

PNG-8 + Alpha 256 色的 PNG 也能支持透明通道

336 |

常用 Photoshop 的你应该会认为 256 色的 PNG-8 虽然体积小,但是却没有透明通道,透明边缘只能是生硬的锯齿,只好使用支持通道的 png-24 。 337 | 而使用 limitPNG 压缩的(模式:有损- 256 色)256 色 PNG 图片,可以拥有透明通道,让你不用为了要使用透明边缘而放弃体积更小的 PNG-8

338 | 339 | 340 |
341 |
342 |
343 | 344 | 345 |
346 | 347 |
348 |
349 |

并发处理 支持同时压缩多个图片

350 |

你可以根据实际情况设置同时压缩的任务数,比如当要处理大量小图时,设置更多的并发任务数可以大幅减少处理时间

351 | 352 | 353 |
354 |
355 |
356 | 357 |
358 |
359 |
360 | 361 |
362 |

这还是个测试中的软件,将来会变得更好

363 |

你可以在下面留言或者通过 微博E-mail 来反馈

364 | 365 |
366 |
367 |
368 | 369 | 370 |
371 | 372 | 373 | 384 | 385 | 386 |
387 |
388 | 389 |
390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 402 | 403 | 408 | 409 | 410 | 411 | 412 | 413 | 414 |
415 | 416 |
417 | 418 | 419 | 420 | 421 | -------------------------------------------------------------------------------- /website/lib/jquery.event.move.js: -------------------------------------------------------------------------------- 1 | // jquery.event.move 2 | // 3 | // 1.3.6 4 | // 5 | // Stephen Band 6 | // 7 | // Triggers 'movestart', 'move' and 'moveend' events after 8 | // mousemoves following a mousedown cross a distance threshold, 9 | // similar to the native 'dragstart', 'drag' and 'dragend' events. 10 | // Move events are throttled to animation frames. Move event objects 11 | // have the properties: 12 | // 13 | // pageX: 14 | // pageY: Page coordinates of pointer. 15 | // startX: 16 | // startY: Page coordinates of pointer at movestart. 17 | // distX: 18 | // distY: Distance the pointer has moved since movestart. 19 | // deltaX: 20 | // deltaY: Distance the finger has moved since last event. 21 | // velocityX: 22 | // velocityY: Average velocity over last few events. 23 | 24 | 25 | (function (module) { 26 | if (typeof define === 'function' && define.amd) { 27 | // AMD. Register as an anonymous module. 28 | define(['jquery'], module); 29 | } else { 30 | // Browser globals 31 | module(jQuery); 32 | } 33 | })(function(jQuery, undefined){ 34 | 35 | var // Number of pixels a pressed pointer travels before movestart 36 | // event is fired. 37 | threshold = 6, 38 | 39 | add = jQuery.event.add, 40 | 41 | remove = jQuery.event.remove, 42 | 43 | // Just sugar, so we can have arguments in the same order as 44 | // add and remove. 45 | trigger = function(node, type, data) { 46 | jQuery.event.trigger(type, data, node); 47 | }, 48 | 49 | // Shim for requestAnimationFrame, falling back to timer. See: 50 | // see http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 51 | requestFrame = (function(){ 52 | return ( 53 | window.requestAnimationFrame || 54 | window.webkitRequestAnimationFrame || 55 | window.mozRequestAnimationFrame || 56 | window.oRequestAnimationFrame || 57 | window.msRequestAnimationFrame || 58 | function(fn, element){ 59 | return window.setTimeout(function(){ 60 | fn(); 61 | }, 25); 62 | } 63 | ); 64 | })(), 65 | 66 | ignoreTags = { 67 | textarea: true, 68 | input: true, 69 | select: true, 70 | button: true 71 | }, 72 | 73 | mouseevents = { 74 | move: 'mousemove', 75 | cancel: 'mouseup dragstart', 76 | end: 'mouseup' 77 | }, 78 | 79 | touchevents = { 80 | move: 'touchmove', 81 | cancel: 'touchend', 82 | end: 'touchend' 83 | }; 84 | 85 | 86 | // Constructors 87 | 88 | function Timer(fn){ 89 | var callback = fn, 90 | active = false, 91 | running = false; 92 | 93 | function trigger(time) { 94 | if (active){ 95 | callback(); 96 | requestFrame(trigger); 97 | running = true; 98 | active = false; 99 | } 100 | else { 101 | running = false; 102 | } 103 | } 104 | 105 | this.kick = function(fn) { 106 | active = true; 107 | if (!running) { trigger(); } 108 | }; 109 | 110 | this.end = function(fn) { 111 | var cb = callback; 112 | 113 | if (!fn) { return; } 114 | 115 | // If the timer is not running, simply call the end callback. 116 | if (!running) { 117 | fn(); 118 | } 119 | // If the timer is running, and has been kicked lately, then 120 | // queue up the current callback and the end callback, otherwise 121 | // just the end callback. 122 | else { 123 | callback = active ? 124 | function(){ cb(); fn(); } : 125 | fn ; 126 | 127 | active = true; 128 | } 129 | }; 130 | } 131 | 132 | 133 | // Functions 134 | 135 | function returnTrue() { 136 | return true; 137 | } 138 | 139 | function returnFalse() { 140 | return false; 141 | } 142 | 143 | function preventDefault(e) { 144 | e.preventDefault(); 145 | } 146 | 147 | function preventIgnoreTags(e) { 148 | // Don't prevent interaction with form elements. 149 | if (ignoreTags[ e.target.tagName.toLowerCase() ]) { return; } 150 | 151 | e.preventDefault(); 152 | } 153 | 154 | function isLeftButton(e) { 155 | // Ignore mousedowns on any button other than the left (or primary) 156 | // mouse button, or when a modifier key is pressed. 157 | return (e.which === 1 && !e.ctrlKey && !e.altKey); 158 | } 159 | 160 | function identifiedTouch(touchList, id) { 161 | var i, l; 162 | 163 | if (touchList.identifiedTouch) { 164 | return touchList.identifiedTouch(id); 165 | } 166 | 167 | // touchList.identifiedTouch() does not exist in 168 | // webkit yet… we must do the search ourselves... 169 | 170 | i = -1; 171 | l = touchList.length; 172 | 173 | while (++i < l) { 174 | if (touchList[i].identifier === id) { 175 | return touchList[i]; 176 | } 177 | } 178 | } 179 | 180 | function changedTouch(e, event) { 181 | var touch = identifiedTouch(e.changedTouches, event.identifier); 182 | 183 | // This isn't the touch you're looking for. 184 | if (!touch) { return; } 185 | 186 | // Chrome Android (at least) includes touches that have not 187 | // changed in e.changedTouches. That's a bit annoying. Check 188 | // that this touch has changed. 189 | if (touch.pageX === event.pageX && touch.pageY === event.pageY) { return; } 190 | 191 | return touch; 192 | } 193 | 194 | 195 | // Handlers that decide when the first movestart is triggered 196 | 197 | function mousedown(e){ 198 | var data; 199 | 200 | if (!isLeftButton(e)) { return; } 201 | 202 | data = { 203 | target: e.target, 204 | startX: e.pageX, 205 | startY: e.pageY, 206 | timeStamp: e.timeStamp 207 | }; 208 | 209 | add(document, mouseevents.move, mousemove, data); 210 | add(document, mouseevents.cancel, mouseend, data); 211 | } 212 | 213 | function mousemove(e){ 214 | var data = e.data; 215 | 216 | checkThreshold(e, data, e, removeMouse); 217 | } 218 | 219 | function mouseend(e) { 220 | removeMouse(); 221 | } 222 | 223 | function removeMouse() { 224 | remove(document, mouseevents.move, mousemove); 225 | remove(document, mouseevents.cancel, mouseend); 226 | } 227 | 228 | function touchstart(e) { 229 | var touch, template; 230 | 231 | // Don't get in the way of interaction with form elements. 232 | if (ignoreTags[ e.target.tagName.toLowerCase() ]) { return; } 233 | 234 | touch = e.changedTouches[0]; 235 | 236 | // iOS live updates the touch objects whereas Android gives us copies. 237 | // That means we can't trust the touchstart object to stay the same, 238 | // so we must copy the data. This object acts as a template for 239 | // movestart, move and moveend event objects. 240 | template = { 241 | target: touch.target, 242 | startX: touch.pageX, 243 | startY: touch.pageY, 244 | timeStamp: e.timeStamp, 245 | identifier: touch.identifier 246 | }; 247 | 248 | // Use the touch identifier as a namespace, so that we can later 249 | // remove handlers pertaining only to this touch. 250 | add(document, touchevents.move + '.' + touch.identifier, touchmove, template); 251 | add(document, touchevents.cancel + '.' + touch.identifier, touchend, template); 252 | } 253 | 254 | function touchmove(e){ 255 | var data = e.data, 256 | touch = changedTouch(e, data); 257 | 258 | if (!touch) { return; } 259 | 260 | checkThreshold(e, data, touch, removeTouch); 261 | } 262 | 263 | function touchend(e) { 264 | var template = e.data, 265 | touch = identifiedTouch(e.changedTouches, template.identifier); 266 | 267 | if (!touch) { return; } 268 | 269 | removeTouch(template.identifier); 270 | } 271 | 272 | function removeTouch(identifier) { 273 | remove(document, '.' + identifier, touchmove); 274 | remove(document, '.' + identifier, touchend); 275 | } 276 | 277 | 278 | // Logic for deciding when to trigger a movestart. 279 | 280 | function checkThreshold(e, template, touch, fn) { 281 | var distX = touch.pageX - template.startX, 282 | distY = touch.pageY - template.startY; 283 | 284 | // Do nothing if the threshold has not been crossed. 285 | if ((distX * distX) + (distY * distY) < (threshold * threshold)) { return; } 286 | 287 | triggerStart(e, template, touch, distX, distY, fn); 288 | } 289 | 290 | function handled() { 291 | // this._handled should return false once, and after return true. 292 | this._handled = returnTrue; 293 | return false; 294 | } 295 | 296 | function flagAsHandled(e) { 297 | e._handled(); 298 | } 299 | 300 | function triggerStart(e, template, touch, distX, distY, fn) { 301 | var node = template.target, 302 | touches, time; 303 | 304 | touches = e.targetTouches; 305 | time = e.timeStamp - template.timeStamp; 306 | 307 | // Create a movestart object with some special properties that 308 | // are passed only to the movestart handlers. 309 | template.type = 'movestart'; 310 | template.distX = distX; 311 | template.distY = distY; 312 | template.deltaX = distX; 313 | template.deltaY = distY; 314 | template.pageX = touch.pageX; 315 | template.pageY = touch.pageY; 316 | template.velocityX = distX / time; 317 | template.velocityY = distY / time; 318 | template.targetTouches = touches; 319 | template.finger = touches ? 320 | touches.length : 321 | 1 ; 322 | 323 | // The _handled method is fired to tell the default movestart 324 | // handler that one of the move events is bound. 325 | template._handled = handled; 326 | 327 | // Pass the touchmove event so it can be prevented if or when 328 | // movestart is handled. 329 | template._preventTouchmoveDefault = function() { 330 | e.preventDefault(); 331 | }; 332 | 333 | // Trigger the movestart event. 334 | trigger(template.target, template); 335 | 336 | // Unbind handlers that tracked the touch or mouse up till now. 337 | fn(template.identifier); 338 | } 339 | 340 | 341 | // Handlers that control what happens following a movestart 342 | 343 | function activeMousemove(e) { 344 | var timer = e.data.timer; 345 | 346 | e.data.touch = e; 347 | e.data.timeStamp = e.timeStamp; 348 | timer.kick(); 349 | } 350 | 351 | function activeMouseend(e) { 352 | var event = e.data.event, 353 | timer = e.data.timer; 354 | 355 | removeActiveMouse(); 356 | 357 | endEvent(event, timer, function() { 358 | // Unbind the click suppressor, waiting until after mouseup 359 | // has been handled. 360 | setTimeout(function(){ 361 | remove(event.target, 'click', returnFalse); 362 | }, 0); 363 | }); 364 | } 365 | 366 | function removeActiveMouse(event) { 367 | remove(document, mouseevents.move, activeMousemove); 368 | remove(document, mouseevents.end, activeMouseend); 369 | } 370 | 371 | function activeTouchmove(e) { 372 | var event = e.data.event, 373 | timer = e.data.timer, 374 | touch = changedTouch(e, event); 375 | 376 | if (!touch) { return; } 377 | 378 | // Stop the interface from gesturing 379 | e.preventDefault(); 380 | 381 | event.targetTouches = e.targetTouches; 382 | e.data.touch = touch; 383 | e.data.timeStamp = e.timeStamp; 384 | timer.kick(); 385 | } 386 | 387 | function activeTouchend(e) { 388 | var event = e.data.event, 389 | timer = e.data.timer, 390 | touch = identifiedTouch(e.changedTouches, event.identifier); 391 | 392 | // This isn't the touch you're looking for. 393 | if (!touch) { return; } 394 | 395 | removeActiveTouch(event); 396 | endEvent(event, timer); 397 | } 398 | 399 | function removeActiveTouch(event) { 400 | remove(document, '.' + event.identifier, activeTouchmove); 401 | remove(document, '.' + event.identifier, activeTouchend); 402 | } 403 | 404 | 405 | // Logic for triggering move and moveend events 406 | 407 | function updateEvent(event, touch, timeStamp, timer) { 408 | var time = timeStamp - event.timeStamp; 409 | 410 | event.type = 'move'; 411 | event.distX = touch.pageX - event.startX; 412 | event.distY = touch.pageY - event.startY; 413 | event.deltaX = touch.pageX - event.pageX; 414 | event.deltaY = touch.pageY - event.pageY; 415 | 416 | // Average the velocity of the last few events using a decay 417 | // curve to even out spurious jumps in values. 418 | event.velocityX = 0.3 * event.velocityX + 0.7 * event.deltaX / time; 419 | event.velocityY = 0.3 * event.velocityY + 0.7 * event.deltaY / time; 420 | event.pageX = touch.pageX; 421 | event.pageY = touch.pageY; 422 | } 423 | 424 | function endEvent(event, timer, fn) { 425 | timer.end(function(){ 426 | event.type = 'moveend'; 427 | 428 | trigger(event.target, event); 429 | 430 | return fn && fn(); 431 | }); 432 | } 433 | 434 | 435 | // jQuery special event definition 436 | 437 | function setup(data, namespaces, eventHandle) { 438 | // Stop the node from being dragged 439 | //add(this, 'dragstart.move drag.move', preventDefault); 440 | 441 | // Prevent text selection and touch interface scrolling 442 | //add(this, 'mousedown.move', preventIgnoreTags); 443 | 444 | // Tell movestart default handler that we've handled this 445 | add(this, 'movestart.move', flagAsHandled); 446 | 447 | // Don't bind to the DOM. For speed. 448 | return true; 449 | } 450 | 451 | function teardown(namespaces) { 452 | remove(this, 'dragstart drag', preventDefault); 453 | remove(this, 'mousedown touchstart', preventIgnoreTags); 454 | remove(this, 'movestart', flagAsHandled); 455 | 456 | // Don't bind to the DOM. For speed. 457 | return true; 458 | } 459 | 460 | function addMethod(handleObj) { 461 | // We're not interested in preventing defaults for handlers that 462 | // come from internal move or moveend bindings 463 | if (handleObj.namespace === "move" || handleObj.namespace === "moveend") { 464 | return; 465 | } 466 | 467 | // Stop the node from being dragged 468 | add(this, 'dragstart.' + handleObj.guid + ' drag.' + handleObj.guid, preventDefault, undefined, handleObj.selector); 469 | 470 | // Prevent text selection and touch interface scrolling 471 | add(this, 'mousedown.' + handleObj.guid, preventIgnoreTags, undefined, handleObj.selector); 472 | } 473 | 474 | function removeMethod(handleObj) { 475 | if (handleObj.namespace === "move" || handleObj.namespace === "moveend") { 476 | return; 477 | } 478 | 479 | remove(this, 'dragstart.' + handleObj.guid + ' drag.' + handleObj.guid); 480 | remove(this, 'mousedown.' + handleObj.guid); 481 | } 482 | 483 | jQuery.event.special.movestart = { 484 | setup: setup, 485 | teardown: teardown, 486 | add: addMethod, 487 | remove: removeMethod, 488 | 489 | _default: function(e) { 490 | var event, data; 491 | 492 | // If no move events were bound to any ancestors of this 493 | // target, high tail it out of here. 494 | if (!e._handled()) { return; } 495 | 496 | function update(time) { 497 | updateEvent(event, data.touch, data.timeStamp); 498 | trigger(e.target, event); 499 | } 500 | 501 | event = { 502 | target: e.target, 503 | startX: e.startX, 504 | startY: e.startY, 505 | pageX: e.pageX, 506 | pageY: e.pageY, 507 | distX: e.distX, 508 | distY: e.distY, 509 | deltaX: e.deltaX, 510 | deltaY: e.deltaY, 511 | velocityX: e.velocityX, 512 | velocityY: e.velocityY, 513 | timeStamp: e.timeStamp, 514 | identifier: e.identifier, 515 | targetTouches: e.targetTouches, 516 | finger: e.finger 517 | }; 518 | 519 | data = { 520 | event: event, 521 | timer: new Timer(update), 522 | touch: undefined, 523 | timeStamp: undefined 524 | }; 525 | 526 | if (e.identifier === undefined) { 527 | // We're dealing with a mouse 528 | // Stop clicks from propagating during a move 529 | add(e.target, 'click', returnFalse); 530 | add(document, mouseevents.move, activeMousemove, data); 531 | add(document, mouseevents.end, activeMouseend, data); 532 | } 533 | else { 534 | // We're dealing with a touch. Stop touchmove doing 535 | // anything defaulty. 536 | e._preventTouchmoveDefault(); 537 | add(document, touchevents.move + '.' + e.identifier, activeTouchmove, data); 538 | add(document, touchevents.end + '.' + e.identifier, activeTouchend, data); 539 | } 540 | } 541 | }; 542 | 543 | jQuery.event.special.move = { 544 | setup: function() { 545 | // Bind a noop to movestart. Why? It's the movestart 546 | // setup that decides whether other move events are fired. 547 | add(this, 'movestart.move', jQuery.noop); 548 | }, 549 | 550 | teardown: function() { 551 | remove(this, 'movestart.move', jQuery.noop); 552 | } 553 | }; 554 | 555 | jQuery.event.special.moveend = { 556 | setup: function() { 557 | // Bind a noop to movestart. Why? It's the movestart 558 | // setup that decides whether other move events are fired. 559 | add(this, 'movestart.moveend', jQuery.noop); 560 | }, 561 | 562 | teardown: function() { 563 | remove(this, 'movestart.moveend', jQuery.noop); 564 | } 565 | }; 566 | 567 | add(document, 'mousedown.move', mousedown); 568 | add(document, 'touchstart.move', touchstart); 569 | 570 | // Make jQuery copy touch event properties over to the jQuery event 571 | // object, if they are not already listed. But only do the ones we 572 | // really need. IE7/8 do not have Array#indexOf(), but nor do they 573 | // have touch events, so let's assume we can ignore them. 574 | if (typeof Array.prototype.indexOf === 'function') { 575 | (function(jQuery, undefined){ 576 | var props = ["changedTouches", "targetTouches"], 577 | l = props.length; 578 | 579 | while (l--) { 580 | if (jQuery.event.props.indexOf(props[l]) === -1) { 581 | jQuery.event.props.push(props[l]); 582 | } 583 | } 584 | })(jQuery); 585 | }; 586 | }); 587 | -------------------------------------------------------------------------------- /website/lib/jquery.twentytwenty.js: -------------------------------------------------------------------------------- 1 | (function($){ 2 | 3 | $.fn.twentytwenty = function(options) { 4 | var options = $.extend({default_offset_pct: 0.5, orientation: 'horizontal'}, options); 5 | return this.each(function() { 6 | 7 | var sliderPct = options.default_offset_pct; 8 | var container = $(this); 9 | var sliderOrientation = options.orientation; 10 | var beforeDirection = (sliderOrientation === 'vertical') ? 'down' : 'left'; 11 | var afterDirection = (sliderOrientation === 'vertical') ? 'up' : 'right'; 12 | 13 | 14 | container.wrap("
"); 15 | container.append("
"); 16 | var beforeImg = container.find("img:first"); 17 | var afterImg = container.find("img:last"); 18 | container.append("
"); 19 | var slider = container.find(".twentytwenty-handle"); 20 | slider.append(""); 21 | slider.append(""); 22 | container.addClass("twentytwenty-container"); 23 | beforeImg.addClass("twentytwenty-before"); 24 | afterImg.addClass("twentytwenty-after"); 25 | 26 | var overlay = container.find(".twentytwenty-overlay"); 27 | overlay.append("
"); 28 | overlay.append("
"); 29 | 30 | var calcOffset = function(dimensionPct) { 31 | var w = beforeImg.width(); 32 | var h = beforeImg.height(); 33 | return { 34 | w: w+"px", 35 | h: h+"px", 36 | cw: (dimensionPct*w)+"px", 37 | ch: (dimensionPct*h)+"px" 38 | }; 39 | }; 40 | 41 | var adjustContainer = function(offset) { 42 | if (sliderOrientation === 'vertical') { 43 | beforeImg.css("clip", "rect(0,"+offset.w+","+offset.ch+",0)"); 44 | } 45 | else { 46 | beforeImg.css("clip", "rect(0,"+offset.cw+","+offset.h+",0)"); 47 | } 48 | container.css("height", offset.h); 49 | }; 50 | 51 | var adjustSlider = function(pct) { 52 | var offset = calcOffset(pct); 53 | slider.css((sliderOrientation==="vertical") ? "top" : "left", (sliderOrientation==="vertical") ? offset.ch : offset.cw); 54 | adjustContainer(offset); 55 | } 56 | 57 | $(window).on("resize.twentytwenty", function(e) { 58 | adjustSlider(sliderPct); 59 | }); 60 | 61 | var offsetX = 0; 62 | var imgWidth = 0; 63 | 64 | slider.on("movestart", function(e) { 65 | if (((e.distX > e.distY && e.distX < -e.distY) || (e.distX < e.distY && e.distX > -e.distY)) && sliderOrientation !== 'vertical') { 66 | e.preventDefault(); 67 | } 68 | else if (((e.distX < e.distY && e.distX < -e.distY) || (e.distX > e.distY && e.distX > -e.distY)) && sliderOrientation === 'vertical') { 69 | e.preventDefault(); 70 | } 71 | container.addClass("active"); 72 | offsetX = container.offset().left; 73 | offsetY = container.offset().top; 74 | imgWidth = beforeImg.width(); 75 | imgHeight = beforeImg.height(); 76 | }); 77 | 78 | slider.on("moveend", function(e) { 79 | container.removeClass("active"); 80 | }); 81 | 82 | slider.on("move", function(e) { 83 | if (container.hasClass("active")) { 84 | sliderPct = (sliderOrientation === 'vertical') ? (e.pageY-offsetY)/imgHeight : (e.pageX-offsetX)/imgWidth; 85 | if (sliderPct < 0) { 86 | sliderPct = 0; 87 | } 88 | if (sliderPct > 1) { 89 | sliderPct = 1; 90 | } 91 | adjustSlider(sliderPct); 92 | } 93 | }); 94 | 95 | container.find("img").on("mousedown", function(event) { 96 | event.preventDefault(); 97 | }); 98 | 99 | $(window).trigger("resize.twentytwenty"); 100 | }); 101 | }; 102 | 103 | })(jQuery); 104 | -------------------------------------------------------------------------------- /website/lib/twentytwenty.css: -------------------------------------------------------------------------------- 1 | .twentytwenty-horizontal .twentytwenty-handle:before, .twentytwenty-horizontal .twentytwenty-handle:after, .twentytwenty-vertical .twentytwenty-handle:before, .twentytwenty-vertical .twentytwenty-handle:after { 2 | content: " "; 3 | display: block; 4 | background: white; 5 | position: absolute; 6 | z-index: 30; 7 | -webkit-box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5); 8 | -moz-box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5); 9 | box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5); } 10 | 11 | .twentytwenty-horizontal .twentytwenty-handle:before, .twentytwenty-horizontal .twentytwenty-handle:after { 12 | width: 3px; 13 | height: 9999px; 14 | left: 50%; 15 | margin-left: -1.5px; } 16 | 17 | .twentytwenty-vertical .twentytwenty-handle:before, .twentytwenty-vertical .twentytwenty-handle:after { 18 | width: 9999px; 19 | height: 3px; 20 | top: 50%; 21 | margin-top: -1.5px; } 22 | 23 | .twentytwenty-before-label, .twentytwenty-after-label, .twentytwenty-overlay { 24 | position: absolute; 25 | top: 0; 26 | width: 100%; 27 | height: 100%; } 28 | 29 | .twentytwenty-before-label, .twentytwenty-after-label, .twentytwenty-overlay { 30 | -webkit-transition-duration: 0.5s; 31 | -moz-transition-duration: 0.5s; 32 | transition-duration: 0.5s; } 33 | 34 | .twentytwenty-before-label, .twentytwenty-after-label { 35 | -webkit-transition-property: opacity; 36 | -moz-transition-property: opacity; 37 | transition-property: opacity; } 38 | 39 | .twentytwenty-before-label:before, .twentytwenty-after-label:before { 40 | color: white; 41 | font-size: 13px; 42 | letter-spacing: 0.1em; } 43 | 44 | .twentytwenty-before-label:before, .twentytwenty-after-label:before { 45 | position: absolute; 46 | background: rgba(255, 255, 255, 0.2); 47 | line-height: 38px; 48 | padding: 0 20px; 49 | -webkit-border-radius: 2px; 50 | -moz-border-radius: 2px; 51 | border-radius: 2px; } 52 | 53 | .twentytwenty-horizontal .twentytwenty-before-label:before, .twentytwenty-horizontal .twentytwenty-after-label:before { 54 | top: 50%; 55 | margin-top: -19px; } 56 | 57 | .twentytwenty-vertical .twentytwenty-before-label:before, .twentytwenty-vertical .twentytwenty-after-label:before { 58 | left: 50%; 59 | margin-left: -45px; 60 | text-align: center; 61 | width: 90px; } 62 | 63 | .twentytwenty-left-arrow, .twentytwenty-right-arrow, .twentytwenty-up-arrow, .twentytwenty-down-arrow { 64 | width: 0; 65 | height: 0; 66 | border: 6px inset transparent; 67 | position: absolute; } 68 | 69 | .twentytwenty-left-arrow, .twentytwenty-right-arrow { 70 | top: 50%; 71 | margin-top: -6px; } 72 | 73 | .twentytwenty-up-arrow, .twentytwenty-down-arrow { 74 | left: 50%; 75 | margin-left: -6px; } 76 | 77 | .twentytwenty-container { 78 | -webkit-box-sizing: content-box; 79 | -moz-box-sizing: content-box; 80 | box-sizing: content-box; 81 | z-index: 0; 82 | overflow: hidden; 83 | position: relative; 84 | -webkit-user-select: none; 85 | -moz-user-select: none; } 86 | .twentytwenty-container img { 87 | max-width: 100%; 88 | position: absolute; 89 | top: 0; 90 | display: block; } 91 | .twentytwenty-container.active .twentytwenty-overlay, .twentytwenty-container.active :hover.twentytwenty-overlay { 92 | background: rgba(0, 0, 0, 0); } 93 | .twentytwenty-container.active .twentytwenty-overlay .twentytwenty-before-label, 94 | .twentytwenty-container.active .twentytwenty-overlay .twentytwenty-after-label, .twentytwenty-container.active :hover.twentytwenty-overlay .twentytwenty-before-label, 95 | .twentytwenty-container.active :hover.twentytwenty-overlay .twentytwenty-after-label { 96 | opacity: 0; } 97 | .twentytwenty-container * { 98 | -webkit-box-sizing: content-box; 99 | -moz-box-sizing: content-box; 100 | box-sizing: content-box; } 101 | 102 | .twentytwenty-before-label { 103 | opacity: 0; } 104 | .twentytwenty-before-label:before { 105 | content: "Before"; } 106 | 107 | .twentytwenty-after-label { 108 | opacity: 0; } 109 | .twentytwenty-after-label:before { 110 | content: "After"; } 111 | 112 | .twentytwenty-horizontal .twentytwenty-before-label:before { 113 | left: 10px; } 114 | 115 | .twentytwenty-horizontal .twentytwenty-after-label:before { 116 | right: 10px; } 117 | 118 | .twentytwenty-vertical .twentytwenty-before-label:before { 119 | top: 10px; } 120 | 121 | .twentytwenty-vertical .twentytwenty-after-label:before { 122 | bottom: 10px; } 123 | 124 | .twentytwenty-overlay { 125 | -webkit-transition-property: background; 126 | -moz-transition-property: background; 127 | transition-property: background; 128 | background: rgba(0, 0, 0, 0); 129 | z-index: 25; } 130 | .twentytwenty-overlay:hover { 131 | background: rgba(0, 0, 0, 0.5); } 132 | .twentytwenty-overlay:hover .twentytwenty-after-label { 133 | opacity: 1; } 134 | .twentytwenty-overlay:hover .twentytwenty-before-label { 135 | opacity: 1; } 136 | 137 | .twentytwenty-before { 138 | z-index: 20; } 139 | 140 | .twentytwenty-after { 141 | z-index: 10; } 142 | 143 | .twentytwenty-handle { 144 | height: 38px; 145 | width: 38px; 146 | position: absolute; 147 | left: 50%; 148 | top: 50%; 149 | margin-left: -22px; 150 | margin-top: -22px; 151 | border: 3px solid white; 152 | -webkit-border-radius: 1000px; 153 | -moz-border-radius: 1000px; 154 | border-radius: 1000px; 155 | -webkit-box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5); 156 | -moz-box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5); 157 | box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5); 158 | z-index: 40; 159 | cursor: pointer; } 160 | 161 | .twentytwenty-horizontal .twentytwenty-handle:before { 162 | bottom: 50%; 163 | margin-bottom: 22px; 164 | -webkit-box-shadow: 0 3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5); 165 | -moz-box-shadow: 0 3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5); 166 | box-shadow: 0 3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5); } 167 | .twentytwenty-horizontal .twentytwenty-handle:after { 168 | top: 50%; 169 | margin-top: 22px; 170 | -webkit-box-shadow: 0 -3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5); 171 | -moz-box-shadow: 0 -3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5); 172 | box-shadow: 0 -3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5); } 173 | 174 | .twentytwenty-vertical .twentytwenty-handle:before { 175 | left: 50%; 176 | margin-left: 22px; 177 | -webkit-box-shadow: 3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5); 178 | -moz-box-shadow: 3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5); 179 | box-shadow: 3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5); } 180 | .twentytwenty-vertical .twentytwenty-handle:after { 181 | right: 50%; 182 | margin-right: 22px; 183 | -webkit-box-shadow: -3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5); 184 | -moz-box-shadow: -3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5); 185 | box-shadow: -3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5); } 186 | 187 | .twentytwenty-left-arrow { 188 | border-right: 6px solid white; 189 | left: 50%; 190 | margin-left: -17px; } 191 | 192 | .twentytwenty-right-arrow { 193 | border-left: 6px solid white; 194 | right: 50%; 195 | margin-right: -17px; } 196 | 197 | .twentytwenty-up-arrow { 198 | border-bottom: 6px solid white; 199 | top: 50%; 200 | margin-top: -17px; } 201 | 202 | .twentytwenty-down-arrow { 203 | border-top: 6px solid white; 204 | bottom: 50%; 205 | margin-bottom: -17px; } 206 | -------------------------------------------------------------------------------- /website/rrr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 10 | --------------------------------------------------------------------------------