├── .gitignore ├── example ├── images │ ├── sq-icon.png │ ├── sq-logo.jpg │ ├── gallery-1.jpg │ ├── gallery-2.jpg │ ├── gallery-3.jpg │ └── pic-home.jpg ├── css │ ├── example-theme.css │ ├── example-material.css │ └── example.css ├── data │ ├── list.json │ └── loadmore │ │ ├── 0 │ │ └── list.json │ │ ├── 1 │ │ └── list.json │ │ ├── 2 │ │ └── list.json │ │ ├── 3 │ │ └── list.json │ │ └── 4 │ │ └── list.json ├── js │ └── example.js ├── example.html ├── column.html ├── panel.html ├── badge.html ├── slider.html ├── core-function.html ├── modal.html ├── grid.html ├── tab.html ├── dropdown.html ├── gallery.html ├── screenshot.html ├── index.html ├── bar.html ├── sticky.html ├── button.html ├── material.html ├── lazyload.html ├── loadmore.html └── animate.html ├── dist └── fonts │ ├── fontawesome-webfont.ttf │ └── fontawesome-webfont.woff ├── src ├── scripts │ ├── squirrel.js │ ├── core │ │ ├── util.js │ │ ├── ua.js │ │ ├── core.js │ │ └── store.js │ ├── dropdown.js │ ├── lazyload.js │ ├── sticky.js │ └── suggest.js └── styles │ ├── utilities.less │ ├── plugins │ ├── panel.less │ ├── tabs.less │ ├── slider.less │ ├── modal.less │ ├── loadmore.less │ ├── dropdown.less │ └── screenshot.less │ ├── badge.less │ ├── gallery.less │ ├── column.less │ ├── squirrel.less │ ├── base.less │ ├── mixins.less │ ├── button.less │ ├── grid.less │ ├── themes │ └── default-theme.less │ ├── list.less │ ├── bar.less │ ├── form.less │ ├── variables.less │ └── normalize.less ├── .jshintrc ├── package.json ├── LICENSE.md ├── README.md ├── doc_tpl ├── documentation.html └── index.html └── gulpfile.js /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/* 2 | /node_modules/* 3 | /bower_components/* -------------------------------------------------------------------------------- /example/images/sq-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iinterest/Squirrel-3/HEAD/example/images/sq-icon.png -------------------------------------------------------------------------------- /example/images/sq-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iinterest/Squirrel-3/HEAD/example/images/sq-logo.jpg -------------------------------------------------------------------------------- /example/images/gallery-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iinterest/Squirrel-3/HEAD/example/images/gallery-1.jpg -------------------------------------------------------------------------------- /example/images/gallery-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iinterest/Squirrel-3/HEAD/example/images/gallery-2.jpg -------------------------------------------------------------------------------- /example/images/gallery-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iinterest/Squirrel-3/HEAD/example/images/gallery-3.jpg -------------------------------------------------------------------------------- /example/images/pic-home.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iinterest/Squirrel-3/HEAD/example/images/pic-home.jpg -------------------------------------------------------------------------------- /dist/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iinterest/Squirrel-3/HEAD/dist/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /dist/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iinterest/Squirrel-3/HEAD/dist/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /src/scripts/squirrel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Bell on 12/20/14. 3 | */ 4 | 5 | $(function() { 6 | 'use strict'; 7 | window.FastClick.attach(document.body); 8 | //alert('hehe') 9 | }); -------------------------------------------------------------------------------- /example/css/example-theme.css: -------------------------------------------------------------------------------- 1 | /*theme example*/ 2 | .sq-bar{ background:#38c483; color:#fff; z-index:101;} 3 | .sq-bar a{ color:#fff;} 4 | 5 | /*.sq-list .list-item,*/ 6 | /*.sq-list .list-item{ border-top-color:#e5e5e5; border-bottom-color:#e5e5e5;}*/ 7 | .sq-list.text .sq-icon{ color:#999;} 8 | 9 | .sq-list .icon{ width:60px; height:60px;} 10 | 11 | -------------------------------------------------------------------------------- /src/styles/utilities.less: -------------------------------------------------------------------------------- 1 | // 2 | // Utilities 3 | // 通用功能样式 4 | // --------------------------------------------------------------------------- 5 | 6 | .@{ns}fl{ float:left;} 7 | .@{ns}fr{ float:right;} 8 | 9 | // 显示 10 | .@{ns}hide{ display:none;} 11 | .@{ns}unvisible{ visibility:hidden;} 12 | .@{ns}clearfix{ .clearfix();} 13 | 14 | // 文字排版 15 | .@{ns}a-line{ .text-hide();} 16 | .@{ns}a-line-ellipsis{ .text-hide-ellipsis();} 17 | -------------------------------------------------------------------------------- /example/css/example-material.css: -------------------------------------------------------------------------------- 1 | .mb--1{ margin-bottom:-1px !important;} 2 | 3 | /*common*/ 4 | .example{ padding:0 10px 40px;} 5 | .example-title{ position:relative; margin:2em 0 .5em; padding:.5em; background:rgba(255, 255, 255, .8); border-bottom:1px solid #3f51b5; font-size:18px; color:#3f51b5; z-index:1; clear:both;} 6 | .example-title:first-of-type{ margin-top:.5em;} 7 | .example-row{ padding:5px;} 8 | .example hr{ margin:20px 0; border:none; border-bottom:1px dashed #eee;} -------------------------------------------------------------------------------- /src/styles/plugins/panel.less: -------------------------------------------------------------------------------- 1 | // 2 | // Panel 3 | // @desc: 下拉菜单,panel.js 配套样式 4 | // @author: squirrel 5 | // --------------------------------------------------------------------------- 6 | .@{ns}panel{ background:@panel-bg;} 7 | 8 | // 动画类,为了适应不同宽度的 panel 动画,具体动画样式写在了 panel.js 中。 9 | .animate-showPanel{ animation-name:showPanel;} 10 | .animate-hidePanel{ animation-name:hidePanel;} 11 | .animate-hideWrap{ animation-name:hideWrap;} 12 | .animate-showWrap{ animation-name:showWrap;} -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "camelcase": true, 4 | "curly": true, 5 | "eqeqeq": true, 6 | "forin": true, 7 | "immed": true, 8 | "indent": 4, 9 | "latedef": false, 10 | "newcap": true, 11 | "noarg": true, 12 | "noempty": true, 13 | "nonew": true, 14 | "quotmark": "single", 15 | "undef": true, 16 | //"unused": true, 17 | "strict": true, 18 | "trailing": true, 19 | "eqnull": true, 20 | "browser": true, 21 | "devel": true, 22 | "jquery": true, 23 | "node": true, 24 | "predef": ["SQ", "seajs", "define"], 25 | "white": false 26 | } -------------------------------------------------------------------------------- /src/styles/badge.less: -------------------------------------------------------------------------------- 1 | // 2 | // Badge 3 | // @desc: 徽章 4 | // --------------------------------------------------------------------------- 5 | 6 | .@{ns}badge{ 7 | border-radius:@badge-border-radius; padding:@badge-padding; font-weight:normal; font-size:@badge-font-size; overflow:hidden; 8 | &:empty{ display:none;} 9 | &.black{ background:@badge-black; color:@white;} 10 | &.blue{ background:@badge-blue; color:@white;} 11 | &.green{ background:@badge-green; color:@white;} 12 | &.grey{ background:@badge-grey; color:@white;} 13 | &.red{ background:@badge-red; color:@white;} 14 | &.orange{ background:@badge-orange; color:@white;} 15 | } -------------------------------------------------------------------------------- /example/data/list.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "data": [ 4 | {"title":"Loadmore 内容1", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 5 | {"title":"Loadmore 内容2", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 6 | {"title":"Loadmore 内容3", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 7 | {"title":"Loadmore 内容4", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 8 | {"title":"Loadmore 内容5", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 9 | {"title":"Loadmore 内容6", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 10 | {"title":"Loadmore 内容7", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"} 11 | ] 12 | } -------------------------------------------------------------------------------- /src/styles/plugins/tabs.less: -------------------------------------------------------------------------------- 1 | // 2 | // Tab 3 | // @desc: 下拉菜单,tab.js 配套样式 4 | // @author: squirrel 5 | // --------------------------------------------------------------------------- 6 | .@{ns}nav-tabs{ 7 | height:39px; border-bottom:1px solid #d7d7d7; 8 | & > li{ 9 | min-width:40px; height:40px; padding:0 10px; border-bottom:3px solid transparent; line-height:40px; text-align:center; cursor:pointer; 10 | &.active{ border-bottom-color:#d7d7d7;} 11 | } 12 | } 13 | .@{ns}tab-content{ 14 | display:none; border:1px solid transparent; min-height:100px; 15 | &.active{ display:block;} 16 | } 17 | .@{ns}tabs-loading-tip{ 18 | padding:30px 0; text-align:center; 19 | .reload{ 20 | &>p{ margin-bottom:.8em;} 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /example/data/loadmore/0/list.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 900, 3 | "data": [ 4 | {"title":"Loadmore 内容1", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 5 | {"title":"Loadmore 内容2", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 6 | {"title":"Loadmore 内容3", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 7 | {"title":"Loadmore 内容4", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 8 | {"title":"Loadmore 内容5", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 9 | {"title":"Loadmore 内容6", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 10 | {"title":"Loadmore 内容7", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"} 11 | ] 12 | } -------------------------------------------------------------------------------- /example/data/loadmore/1/list.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "data": [ 4 | {"title":"Loadmore 内容1", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 5 | {"title":"Loadmore 内容2", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 6 | {"title":"Loadmore 内容3", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 7 | {"title":"Loadmore 内容4", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 8 | {"title":"Loadmore 内容5", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 9 | {"title":"Loadmore 内容6", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 10 | {"title":"Loadmore 内容7", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"} 11 | ] 12 | } -------------------------------------------------------------------------------- /example/data/loadmore/2/list.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "data": [ 4 | {"title":"Loadmore 内容1", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 5 | {"title":"Loadmore 内容2", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 6 | {"title":"Loadmore 内容3", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 7 | {"title":"Loadmore 内容4", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 8 | {"title":"Loadmore 内容5", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 9 | {"title":"Loadmore 内容6", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 10 | {"title":"Loadmore 内容7", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"} 11 | ] 12 | } -------------------------------------------------------------------------------- /example/data/loadmore/3/list.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "data": [ 4 | {"title":"Loadmore 内容1", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 5 | {"title":"Loadmore 内容2", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 6 | {"title":"Loadmore 内容3", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 7 | {"title":"Loadmore 内容4", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 8 | {"title":"Loadmore 内容5", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 9 | {"title":"Loadmore 内容6", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 10 | {"title":"Loadmore 内容7", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"} 11 | ] 12 | } -------------------------------------------------------------------------------- /example/data/loadmore/4/list.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "data": [ 4 | {"title":"Loadmore 内容1", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 5 | {"title":"Loadmore 内容2", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 6 | {"title":"Loadmore 内容3", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 7 | {"title":"Loadmore 内容4", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 8 | {"title":"Loadmore 内容5", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 9 | {"title":"Loadmore 内容6", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}, 10 | {"title":"Loadmore 内容7", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"} 11 | ] 12 | } -------------------------------------------------------------------------------- /src/styles/plugins/slider.less: -------------------------------------------------------------------------------- 1 | // 2 | // Slider 3 | // @desc: 滑动图片,slider.js 配套样式 4 | // @author: squirrel 5 | // --------------------------------------------------------------------------- 6 | .@{ns}slider{ 7 | display:block; 8 | li{} 9 | img{ display:block; margin:0 auto;} 10 | } 11 | 12 | .bx-wrapper{ 13 | position:relative; 14 | 15 | .bx-controls{} 16 | .bx-pager{ position:absolute; right:10px; bottom:10px; text-align:right;} 17 | .bx-pager-item{ display:inline-block; padding:0 3px;} 18 | .bx-pager-link{ 19 | display:inline-block; width:8px; height:8px; background:rgba(0,0,0,.6); border-radius:999rem; font-size:0; overflow:hidden; 20 | &.active{ background:#38c483;} 21 | } 22 | 23 | .bx-controls-direction{ display:none; } 24 | .bx-prev{} 25 | .bx-next{} 26 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "squirrel", 3 | "version": "0.10.0", 4 | "description": "", 5 | "main": "gulpfile.js", 6 | "devDependencies": { 7 | "del": "^0.1.3", 8 | "gulp-autoprefixer": "^1.0.1", 9 | "gulp-cache": "^0.2.4", 10 | "gulp-concat": "^2.4.1", 11 | "gulp-footer": "^1.0.5", 12 | "gulp-header": "^1.2.2", 13 | "gulp-if": "^1.2.5", 14 | "gulp-imagemin": "^1.2.1", 15 | "gulp-jshint": "^1.8.5", 16 | "gulp-less": "^1.3.6", 17 | "gulp-livereload": "^2.1.1", 18 | "gulp-minify-css": "^0.3.11", 19 | "gulp-notify": "^2.0.1", 20 | "gulp-plumber": "^0.6.6", 21 | "gulp-rename": "^1.2.0", 22 | "gulp-replace": "^0.5.0", 23 | "gulp-sourcemaps": "^1.2.4", 24 | "gulp-uglify": "^1.0.1" 25 | }, 26 | "author": "iinterest.bell@gmail.com", 27 | "license": "ISC" 28 | } 29 | -------------------------------------------------------------------------------- /src/styles/gallery.less: -------------------------------------------------------------------------------- 1 | // 2 | // Gallery 3 | // @desc: 画廊 4 | // --------------------------------------------------------------------------- 5 | 6 | .@{ns}gallery{ 7 | width:100%; overflow:hidden; 8 | ul{ margin:-@gallery-space;} 9 | a{ display:block; position:relative;} 10 | img{ display:block; width:100%;} 11 | 12 | .gallery-item{ position:relative; padding:@gallery-space;} 13 | .title{ font-size:@gallery-title-font-size;} 14 | .desc{ font-size:@gallery-title-font-desc;} 15 | 16 | 17 | // overlay 18 | // @desc: 浮层标题 19 | // -------------------------------------------- 20 | &.overlay{ 21 | .title{ position:absolute; right:0; bottom:0; left:0; margin:0; padding:@gallery-space; background:@gallery-overlay-title-bg; color:@gallery-overlay-title-color;} 22 | .desc{ display:none;} 23 | } 24 | } -------------------------------------------------------------------------------- /src/styles/column.less: -------------------------------------------------------------------------------- 1 | // 2 | // Column Grid 3 | // 分栏 4 | // --------------------------------------------------------------------------- 5 | 6 | .make-col-grid(@point: sm, @counter: @grid-columns, @i: 1) when (@i =< @counter) { 7 | .@{ns}col-@{point}-@{i} > li, 8 | .@{ns}col-@{point}-@{i} > a{ 9 | width: (100% / @i); 10 | &:nth-of-type(n) { 11 | clear: none; 12 | } 13 | &:nth-of-type(@{i}n+1) { 14 | clear: both; 15 | } 16 | } 17 | .make-col-grid(@point, @counter, (@i + 1)); 18 | } 19 | 20 | 21 | [class*="@{ns}col-"] { 22 | display:block; padding:0; margin:0; .clearfix(); 23 | &> li, 24 | &> a{ display:block; float:left; height:auto;} 25 | } 26 | 27 | 28 | @media @mobile-screen { 29 | .make-col-grid(mb); 30 | } 31 | 32 | @media @table-screen { 33 | .make-col-grid(tb); 34 | } -------------------------------------------------------------------------------- /src/styles/plugins/modal.less: -------------------------------------------------------------------------------- 1 | // 2 | // Modal 3 | // @desc: 弹出窗口,modal.js 配套样式 4 | // @author: squirrel 5 | // --------------------------------------------------------------------------- 6 | .@{ns}modal{ 7 | background:@modal-bg; border-radius:@modal-radius; 8 | 9 | //.content{} 10 | 11 | // 按钮 12 | .close-btn{ position:absolute; top:0; right:0; width:30px; height:30px; background:#d7d7d7; border-radius:3px; text-align:center; line-height:30px; cursor:pointer; z-index:3;} 13 | .ok-btn, 14 | .cancel-btn{ position:absolute; width:50%; padding:@modal-btn-padding; background:#efefef; border-top:1px solid @modal-border-color; text-align:center; cursor:pointer;} 15 | .ok-btn{ left:0; bottom:0; border-radius:0 0 0 @modal-radius;} 16 | .cancel-btn{ right:0; bottom:0; border-left:1px solid @modal-border-color; border-radius:0 0 @modal-radius 0;} 17 | } -------------------------------------------------------------------------------- /src/styles/plugins/loadmore.less: -------------------------------------------------------------------------------- 1 | // 2 | // Loadmore 3 | // @desc: 下拉菜单,loadmore.js 配套样式 4 | // @author: squirrel 5 | // --------------------------------------------------------------------------- 6 | .@{ns}loadmore-state{ 7 | position:relative; padding:@loadmore-padding; line-height:@loadmore-lin-height; color:@default-font-color; text-align:center; cursor:pointer; overflow:hidden; 8 | .state-icon{ 9 | &::before{ font-family:@icon-font-family;} 10 | } 11 | .state-txt{ padding:0 8px;} 12 | &.click{ 13 | & > .state-icon::before{ content:'\f103';} 14 | } 15 | &.loading{ 16 | & > .state-icon{ position:relative; top:-1px; animation:icon-spin 2s infinite linear;} 17 | & > .state-icon::before{ content:'\f110';} 18 | } 19 | //&.error{} 20 | &.no-more{ 21 | & > .state-icon::before{ content:'';} 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/styles/squirrel.less: -------------------------------------------------------------------------------- 1 | // 2 | // Squirrel LESS 3 | // --------------------------------------------------------------------------- 4 | 5 | // Variables and mixins 6 | @import "variables"; 7 | @import "mixins"; 8 | 9 | 10 | // Normalize and Core 11 | @import "normalize"; 12 | @import "base"; 13 | @import "utilities"; 14 | 15 | 16 | // CSS 17 | @import "button"; 18 | @import "column"; 19 | @import "form"; 20 | @import "grid"; 21 | 22 | 23 | // Components 24 | @import "animate"; 25 | @import "badge"; 26 | @import "bar"; 27 | @import "gallery"; 28 | @import "list"; 29 | @import "font-awesome"; 30 | 31 | 32 | // Plugins 33 | @import "plugins/dropdown"; 34 | @import "plugins/loadmore"; 35 | @import "plugins/panel"; 36 | @import "plugins/modal"; 37 | @import "plugins/tabs"; 38 | 39 | @import "plugins/slider"; 40 | @import "plugins/screenshot"; 41 | 42 | 43 | // Theme 44 | //@import "themes/default-theme"; -------------------------------------------------------------------------------- /src/styles/base.less: -------------------------------------------------------------------------------- 1 | // 2 | // Base 3 | // @desc: 定制化样式 4 | // --------------------------------------------------------------------------- 5 | 6 | *, 7 | *:before, 8 | *:after{ box-sizing:border-box; margin:0; padding:0;} 9 | 10 | html{ font-size:62.5%;} 11 | 12 | body{ 13 | //-webkit-font-smoothing:antialiased; 14 | //** 暂时屏蔽 15 | //-webkit-tap-highlight-color:transparent; 16 | -webkit-touch-callout:none; 17 | 18 | font-family:@font-family; 19 | font-size:@default-font-size; 20 | color:@default-font-color; 21 | word-wrap:break-word; 22 | break-word:break-all; 23 | } 24 | 25 | h1, 26 | h2, 27 | h3, 28 | h4, 29 | h5{ font-weight:400;} 30 | 31 | ul, 32 | ol, 33 | menu{ list-style:none;} 34 | 35 | input, 36 | textarea, 37 | select{ -webkit-font-smoothing:antialiased;} 38 | 39 | a{ color:@default-font-color; text-decoration:none;} 40 | b, 41 | i, 42 | em{ font-weight:normal; font-style:normal;} 43 | -------------------------------------------------------------------------------- /src/styles/plugins/dropdown.less: -------------------------------------------------------------------------------- 1 | // 2 | // Dropdown 3 | // @desc: 下拉菜单,dropdown.js 配套样式 4 | // @author: squirrel 5 | // --------------------------------------------------------------------------- 6 | .@{ns}dropdown{ 7 | position:relative; display:inline-block; vertical-align:middle; 8 | 9 | .dropdown-content{ 10 | display:none; position:absolute; top:100%; left:0; min-width:@dropdown-min-width; margin-top:-1px; background:@dropdown-content-bg; box-shadow:@shardow-normal; border-radius:@dropdown-content-radius; border:1px solid @dropdown-border-color; z-index:@dropdown-index; 11 | & > li{ 12 | padding:@dropdown-content-padding; border-bottom:1px solid @dropdown-border-color; 13 | &:hover{ background:@dropdown-hover;} 14 | &:last-of-type{ border-bottom:none;} 15 | } 16 | a{ display:block; white-space:nowrap; line-height:@default-line-height;} 17 | &.align-right{ left:auto; right:0;} 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /example/js/example.js: -------------------------------------------------------------------------------- 1 | (function init() { 2 | 'use strict'; 3 | $('.J_menu').dropdown({ 4 | ANIMATE: '.fadeIn quick' 5 | }); 6 | 7 | $('.J_fixedGoTop').sticky({ 8 | ARRY_FIXED_POSITION: ['auto', 10, 20, 'auto'], 9 | NUM_TRIGGER_POSITION: 300, 10 | fixedIn: function () { 11 | this.$element.show(); 12 | }, 13 | fixedOut: function () { 14 | this.$element.hide(); 15 | } 16 | }); 17 | 18 | $('.J_goBack').on('click', function (e) { 19 | e.preventDefault(); 20 | history.go(-1); 21 | }); 22 | }()); 23 | 24 | var $animateDemo = $('#animate-demo'); 25 | $('.animate-effect').on('click', '.sq-btn', function (e) { 26 | 'use strict'; 27 | e.preventDefault(); 28 | var $btn = $(this); 29 | var animateEffect = $btn.attr('data-effect'); 30 | $btn.addClass('mt-green').siblings().removeClass('mt-green'); 31 | $animateDemo.removeClass().addClass('animated ' + animateEffect); 32 | }); 33 | 34 | 35 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2010 - 2030 iinterest, [http://hisquirrel.com]() 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /src/styles/plugins/screenshot.less: -------------------------------------------------------------------------------- 1 | //** sq-screenshot 2 | //** Screenshot 3 | 4 | .@{ns}screenshot{ 5 | .screenshot-horizontal{ 6 | margin-top:20px; max-height:400px; overflow:hidden; 7 | // 默认为全景模式 8 | ul{ list-style:none; margin:0; padding:0; overflow:hidden;} 9 | li{ float:left; width:320px; margin:0 10px 0 0; padding:0; background:#e5e5e5; text-align:center; cursor:pointer; 10 | img{ display:block; width:100%;} 11 | &.active{ color:#fff; background:#e5e5e5;} 12 | } 13 | // 肖像模式 14 | .portrait-view{ 15 | li{ width:200px;} 16 | } 17 | .landscape-view{} 18 | } 19 | 20 | /* Scrollbar */ 21 | .screenshot-scrollbar{ 22 | margin: 10px 0 0 0; 23 | height: 5px; 24 | background: #e5e5e5; 25 | line-height: 0; 26 | 27 | .handle { 28 | width: 100px; 29 | height: 100%; 30 | background: #38c483; 31 | cursor: pointer; 32 | 33 | .mousearea { 34 | position: absolute; 35 | top: -9px; 36 | left: 0; 37 | width: 100%; 38 | height: 20px; 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/styles/mixins.less: -------------------------------------------------------------------------------- 1 | // 2 | // Mixin 3 | // @desc: LESS 混合模块 4 | // --------------------------------------------------------------------------- 5 | 6 | // 清除浮动 7 | .clearfix() { 8 | &:before, 9 | &:after { 10 | content: " "; 11 | display: table; 12 | } 13 | &:after { 14 | clear: both; 15 | } 16 | } 17 | 18 | // 折行字体隐藏 19 | .text-hide() { 20 | display:block; 21 | max-height:1.4em; 22 | line-height:1.4em; 23 | overflow:hidden; 24 | } 25 | 26 | // 折行字体显示省略号 27 | .text-hide-ellipsis() { 28 | display:block; 29 | white-space:nowrap; 30 | text-overflow:ellipsis; 31 | overflow:hidden; 32 | } 33 | 34 | // 元素平铺 35 | .overlay (@top: 0, @right: 0, @bottom: 0, @left: 0){ 36 | position:absolute; 37 | top: @top; 38 | right: @right; 39 | bottom: @bottom; 40 | left: @left; 41 | } 42 | 43 | // 三角箭头 44 | .sq-arr-up (@size, @color){ 45 | width:0; height:0; border:@size solid transparent; border-bottom-color:@color; 46 | } 47 | .sq-arr-right (@size, @color){ 48 | width:0; height:0; border:@size solid transparent; border-right-color:@color; 49 | } 50 | .sq-arr-down (@size, @color){ 51 | width:0; height:0; border:@size solid transparent; border-down-color:@color; 52 | } 53 | .sq-arr-left (@size, @color){ 54 | width:0; height:0; border:@size solid transparent; border-left-color:@color; 55 | } -------------------------------------------------------------------------------- /example/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Squirrel Example 11 | 12 | 13 |
14 | 26 |
27 |

Example

28 |
29 |
30 | Top 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /example/css/example.css: -------------------------------------------------------------------------------- 1 | .mb--1{ margin-bottom:-1px !important;} 2 | 3 | /*common*/ 4 | .example{ padding:0 10px 40px;} 5 | .example-title{ position:relative; margin:2em 0 .5em; padding:.5em; background:rgba(255, 255, 255, .8); border-bottom:1px solid #38c483; font-size:18px; color:#38c483; z-index:1; clear:both;} 6 | .example-title:first-of-type{ margin-top:.5em;} 7 | .example-row{ padding:5px;} 8 | .example hr{ margin:20px 0; border:none; border-bottom:1px dashed #eee;} 9 | .footer{ height:20px; margin-top:50px; background:#eee; text-align:center; font-size:12px; line-height:20px;} 10 | .example-space{ height:700px;} 11 | .go-top-btn{ display:none; padding:10px; border-radius:999em; background:#38c483; color:#fff;} 12 | /*animate*/ 13 | .example .animate-box{ padding:10px; height:150px;} 14 | .example #animate-demo{ position:fixed; top:70px; left:50%; width:240px; height:130px; margin-left:-120px; background:rgba(0, 0, 0, .6); line-height:130px; text-align:center; border-radius:4px; color:#fff; z-index:10;} 15 | .example .animate-effect h3{ font-size:16px;} 16 | .example .animate-effect .sq-btn{ margin-bottom:5px;} 17 | /*bar*/ 18 | .example .sq-bar{ background:#eee; color:#333;} 19 | .example .sq-bar a{ color:#333;} 20 | .example .sq-bar .active{ color:#fff;} 21 | /*glyphicon*/ 22 | .example .fontawesome-icon-list > div{ margin:10px 0; font-size:12px;} 23 | /*grid*/ 24 | .example-grid > .row > div{ background:#eee; border:1px solid #38c483;} 25 | 26 | 27 | .sq-slider{ height:150px; background:#eee; overflow:hidden;} 28 | .sq-slider li{ height:150px;} 29 | .bx-wrapper{} 30 | .bx-wrapper .bx-pager{} 31 | .bx-wrapper .bx-pager-item{} 32 | .bx-wrapper .bx-pager-link{} 33 | .bx-wrapper .bx-pager-link.active{ background:#38c483;} 34 | .bx-wrapper .bx-controls-direction{} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Squirrle 3 2 | 3 | ## 一、简介 4 | 5 | Squirrle 是一款轻量级的移动 Web 前端开发框架,提供简单、快速的 Web 开发体验。 6 | 7 | ## 二、特性 8 | 9 | * 专为移动终端设计,支持 Android、iOS、WP 操作系统。 10 | * 适应各种屏幕尺寸、分辨率的移动终端,针对触摸操作体验进行了优化。 11 | * 遵循 MIT 协议,无论公司还是个人,都可以免费、自由使用。 12 | * 提供丰富的样式及交互组件,能帮助您轻松构建 Web 应用。 13 | * 采用 LESS 技术、CMD 规范、jQuery 插件开发模式,扩展方便。 14 | * 提供定制工具,可以根据实际需要灵活的定制框架。 15 | 16 | ## 三、使用指南 17 | 18 | ### 1、安装 19 | 20 | Squirrel 支持两种安装方式: 21 | 22 | 1. 直接下载 Squirrle 3 [代码包](https://github.com/iinterest/Squirrel-3/releases) 23 | 2. 或者使用 npm 安装: 24 | 25 | npm install squirrel-pt 26 | 27 | 安装完成或压缩下载的安装包之后,可看到以下目录结构: 28 | 29 | squirrel/ 30 | | 31 | ├── css/ 32 | │ ├── squirrel.min.css 33 | │ └── app.css 34 | ├── fonts/ 35 | │ ├── fontawesome-webfont.ttf 36 | │ └── fontawesome-webfont.woff 37 | ├── images/ 38 | ├── js/ 39 | │ ├── jquery.min.js 40 | │ ├── jquery.min.map 41 | │ ├── squirrel.min.js 42 | │ └── app.js 43 | ├── maps/ 44 | │ ├── squirrel.min.css.map 45 | │ └── squirrel.min.js.map 46 | └── index.html 47 | 48 | ### 2、使用 49 | 50 | Squirrel 安装包内包含编译并压缩好的 squirrel.min.css、squirrel.min.js 文件,以及最新的 jQuery 库和 Awesome 的图标字体,同时还提供 Javascript 和 CSS 的源码映射表(.map 文件);除此之外还提供简单的模板样式文件(Boilerplate)您可以: 51 | 52 | * 以index.html为样板编写 HTML; 53 | * 在app.css中编写 CSS; 54 | * 在app.js中编写 JavaScript; 55 | * 将图片资源放在images目录下。 56 | 57 | ## 三、贡献力量 58 | 59 | Squirrel 3 是遵循 MIT 协议的开源项目,代码寄托于 [GitHub](https://github.com/iinterest/Squirrel-3),如果感兴趣请加入我们贡献您的力量,您可以: 60 | 61 | 1. 希望您能反馈任何有关体验方面的意见和建议至 [Github issues](https://github.com/iinterest/Squirrel-3/issues)「体验」,您可以提出问题或改进建议,甚至提出新的功能需求。 62 | 2. 如果使用中发现 BUG,请提交至 [Github issues](https://github.com/iinterest/Squirrel-3/issues)「BUG」,同时也请报告您的使用版本、环境,我们会尽快确认、修复。 63 | 3. 欢迎您为 Squirrel 贡献高实用性的功能插件,以方便其他使用者。开始之前请先阅读插件开发流程。 64 | 4. 如果您制作了一款基于 Squirrel 的主题并且符合主题的开发规范,我们非常欢迎您将它提交至主题库。 65 | 5. DPL 组件与业务结合紧密,具备高质量且完整的交互体验,如果您为您的项目开发了类似的组件,欢迎提交至设计模式库。 66 | 67 | -------------------------------------------------------------------------------- /example/column.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Squirrel Column 11 | 12 | 13 |
14 | 26 |
27 |

Column

28 |
29 |
    30 |
  • 1
  • 31 |
  • 2
  • 32 |
  • 3
  • 33 |
  • 4
  • 34 |
  • 5
  • 35 |
36 |
37 |
38 |
39 | Top 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/panel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Squirrel Panel 11 | 12 | 13 |
14 | 26 |
27 |

Panel

28 | 29 |
30 |
31 | Top 32 | 33 | 34 | 35 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /example/badge.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Squirrel Badge 11 | 12 | 13 |
14 | 26 |
27 |

Badge

28 | Hot 29 | 29 30 | 最新 31 | 首发 32 | badge 33 | badge 34 | 35 |
36 |
37 | Top 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /example/slider.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Squirrel Slider 11 | 12 | 13 |
14 | 26 |
27 |

Slider

28 | 33 |
34 |
35 |
36 | Top 37 | 38 | 39 | 40 | 41 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /example/core-function.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Squirrel Example CoreFun 11 | 12 | 13 |
14 | 23 |
24 |
25 |
26 | 27 | 28 |
29 |
30 | 31 | 32 |
33 |
34 |
35 | 36 | 37 | 38 | 39 | 48 |
49 | 50 | -------------------------------------------------------------------------------- /example/modal.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Squirrel Modal 11 | 12 | 13 |
14 | 26 |
27 |

Modal

28 | 29 |
30 |
31 | Top 32 | 33 | 39 | 40 | 41 | 42 | 43 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /example/grid.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Squirrel Grid 11 | 12 | 13 |
14 | 26 |
27 |

Grid

28 |
29 |
30 |
col-mb-4
31 |
col-mb-4
32 |
col-mb-4
33 |
34 |
35 |
col-mb-6
36 |
col-mb-6
37 |
38 |
39 |
col-mb-8
40 |
col-mb-2
41 |
col-mb-2
42 |
43 |
44 |
45 |
46 | Top 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /example/tab.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Squirrel Tab 11 | 12 | 13 |
14 | 26 |
27 |

Tab

28 |
29 |
    30 |
  • tab 1
  • 31 |
  • tab 2
  • 32 |
  • tab 3
  • 33 |
34 |
35 |

Proin elit arcu, ...

36 |
37 |
38 |

Morbi tincidunt, ...

39 |
40 |
41 |

Duis cursus ...

42 |
43 |
44 |
45 |
46 | Top 47 | 48 | 49 | 50 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/scripts/core/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file SQ.util 3 | * 常用函数 4 | * @version 1.0.0 5 | */ 6 | /*global SQ*/ 7 | SQ.util = { 8 | /** 9 | * 随机数输出 10 | * @method 11 | * @name SQ.util.generate 12 | * @example 13 | * Sq.util.generate.uniqueId(); 14 | * Sq.util.generate.randomInt(0, 9); 15 | * Sq.util.generate.randomArr([1,2,3]); 16 | */ 17 | generate: { 18 | // 生成唯一标识符 19 | uniqueId: function () { 20 | 21 | }, 22 | randomInt: function (min, max) { 23 | 'use strict'; 24 | if (typeof min === 'number' && typeof max === 'number' && min < max) { 25 | return parseInt(Math.random() * (max - min + 1) + min, 10); 26 | } 27 | return false; 28 | }, 29 | randomArr: function (arr) { 30 | 'use strict'; 31 | return arr.sort(function () { 32 | return Math.random() - 0.5; 33 | }); 34 | } 35 | }, 36 | /** 37 | * 字符串操作 38 | * @method 39 | * @name SQ.util.string 40 | * @example 41 | * SQ.util.string.trim(' test string '); 42 | * //return test string 43 | */ 44 | string: { 45 | // 过滤字符串首尾的空格 46 | trim: function(srt) { 47 | 'use strict'; 48 | return srt.replace(/^\s+|\s+$/g, ''); 49 | } 50 | }, 51 | /** 52 | * 格式化时间 53 | * @method 54 | * @name SQ.util.dateToString 55 | * @example 56 | * SQ.util.dateToString(new Date()) 57 | * //return 2013-10-17 17:31:58 58 | */ 59 | dateToString: function(time) { 60 | 'use strict'; 61 | var year = time.getFullYear(); 62 | var month = time.getMonth() + 1; 63 | var date = time.getDate(); 64 | var hours = time.getHours(); 65 | var min = time.getMinutes(); 66 | var sec = time.getSeconds(); 67 | 68 | month = month < 10 ? ('0' + month) : month; 69 | date = date < 10 ? ('0' + date) : date; 70 | hours = hours < 10 ? ('0' + hours) : hours; 71 | min = min < 10 ? ('0' + min) : min; 72 | sec = sec < 10 ? ('0' + sec) : sec; 73 | 74 | var dateString = year + '-' + month + '-' + date + ' ' + hours + ':' + min + ':' + sec; 75 | return dateString; 76 | }, 77 | goTop: function (e) { 78 | 'use strict'; 79 | e.preventDefault(); 80 | window.scrollTo(0, 0); 81 | }, 82 | goBack: function (e) { 83 | 'use strict'; 84 | e.preventDefault(); 85 | history.back(); 86 | } 87 | }; -------------------------------------------------------------------------------- /src/styles/button.less: -------------------------------------------------------------------------------- 1 | // 2 | // Button 3 | // @desc: 通用按钮,包括单体按钮、组合按钮。 4 | // --------------------------------------------------------------------------- 5 | 6 | .@{ns}btn{ 7 | display:inline-block; position:relative; border-radius:@btn-border-radius; border:1px solid transparent; text-align:center; white-space:nowrap; line-height:1; cursor:pointer; outline:none; user-select:none; 8 | 9 | 10 | // Size 11 | // @desc: 按钮尺寸 12 | // -------------------------------------------- 13 | padding:@btn-padding-normal; 14 | &.large{ padding:@btn-padding-large; font-size:@btn-font-size-large;} 15 | &.small{ padding:@btn-padding-small; font-size:@btn-font-size-small;} 16 | &.mini{ padding:@btn-padding-mini; font-size:@btn-font-size-mini;} 17 | &.block{ display:block; width:100%; clear:both;} 18 | 19 | 20 | // disabled 21 | // @desc: 禁用样式 22 | // -------------------------------------------- 23 | &.disabled, 24 | &[disabled]{ 25 | pointer-events:none; cursor:not-allowed; 26 | opacity:@btn-disabled-opacity; 27 | } 28 | 29 | // color 30 | // 31 | &.mt-white{ 32 | background:#f7f7f7; color:#333; border-color:#ddd; 33 | & > a{ color:#333;} 34 | } 35 | &.mt-grey{ 36 | background:#e1e1e1; border-color:#e1e1e1; color:#333; 37 | & > a{ color:#333;} 38 | } 39 | &.mt-black{ 40 | background:#444; border-color:#444; color:#fff; 41 | & > a{ color:#fff;} 42 | } 43 | &.mt-blue{ 44 | background:#4285f4; border-color:#4285f4; color:#fff; 45 | & > a{ color:#fff;} 46 | &:hover{ background:#3367d6;} 47 | } 48 | &.mt-green{ background:#6eb82c; border-color:#6eb82c; color:#fff; 49 | & > a{ color:#fff;} 50 | } 51 | &.mt-orange{ background:#faa800; border-color:#faa800; color:#fff; 52 | & > a{ color:#fff;} 53 | } 54 | &.mt-red{ background:#e34541; border-color:#e34541; color:#fff; 55 | & > a{ color:#fff;} 56 | } 57 | } 58 | 59 | 60 | // Button Group 61 | // @desc: 组合按钮 62 | // -------------------------------------------- 63 | .sq-btn-group{ 64 | display:flex; flex-direction:row; 65 | .active{ 66 | z-index:1; // 保证激活项目在最上层显示 67 | } 68 | & > .sq-btn{ 69 | display:block; flex:1; margin-left:-1px; border-radius:0; .text-hide-ellipsis(); 70 | &:first-child{ margin-left:0; border-radius:@btn-border-radius 0 0 @btn-border-radius;} 71 | &:last-child{ border-radius:0 @btn-border-radius @btn-border-radius 0;} 72 | } 73 | // 垂直布局 74 | &.vertical{ 75 | flex-direction:column; 76 | & > .sq-btn{ 77 | margin:-1px 0 0 0; 78 | &:first-child{ margin-top:0; border-radius:@btn-border-radius @btn-border-radius 0 0;} 79 | &:last-child{ border-radius:0 0 @btn-border-radius @btn-border-radius;} 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /src/scripts/core/ua.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file SQ.ua 3 | * 获取设备 ua 信息,判断系统版本、浏览器名称及版本 4 | * @version 1.0.0 5 | */ 6 | /*global SQ*/ 7 | SQ.ua = (function () { 8 | 'use strict'; 9 | var info = {}; 10 | var ua = navigator.userAgent; 11 | var m; 12 | 13 | info.os = {}; 14 | info.browser = {}; 15 | 16 | /** 17 | * operating system. android, ios, linux, windows 18 | * @type string 19 | */ 20 | if ((/Android/i).test(ua)) { 21 | info.os.name = 'android'; 22 | info.os.version = ua.match(/(Android)\s([\d.]+)/)[2]; 23 | } else if ((/Adr/i).test(ua)) { 24 | // UC 浏览器极速模式下,Android 系统的 UA 为 'Adr' 25 | info.os.name = 'android'; 26 | info.os.version = ua.match(/(Adr)\s([\d.]+)/)[2]; 27 | } else if ((/iPod/i).test(ua)) { 28 | info.os.name = 'ios'; 29 | info.os.version = ua.match(/OS\s([\d_]+)/)[1].replace(/_/g, '.'); 30 | info.device = 'ipod'; 31 | } else if ((/iPhone/i).test(ua)) { 32 | info.os.name = 'ios'; 33 | info.os.version = ua.match(/(iPhone\sOS)\s([\d_]+)/)[2].replace(/_/g, '.'); 34 | info.device = 'iphone'; 35 | } else if ((/iPad/i).test(ua)) { 36 | info.os.name = 'ios'; 37 | info.os.version = ua.match(/OS\s([\d_]+)/)[1].replace(/_/g, '.'); 38 | info.device = 'ipad'; 39 | } 40 | 41 | // 浏览器判断 42 | m = ua.match(/AppleWebKit\/([\d.]*)/); 43 | if (m && m[1]) { 44 | info.browser.core = 'webkit'; 45 | info.browser.version = m[1]; 46 | 47 | if ((/Chrome/i).test(ua)) { 48 | info.browser.shell = 'chrome'; 49 | } else if ((/Safari/i).test(ua)) { 50 | info.browser.shell = 'safari'; 51 | } else if ((/Opera/i).test(ua)) { 52 | info.browser.shell = 'opera'; 53 | } 54 | } 55 | 56 | if ((/UCBrowser/i).test(ua)) { 57 | // UCWeb 9.0 UA 信息中包含 UCBrowser 字段 58 | m = ua.match(/(UCBrowser)\/([\d.]+)/); 59 | info.browser.shell = 'ucweb'; 60 | info.browser.version = m[2]; 61 | } else if ((/UCWEB/i).test(ua)) { 62 | // UCWeb 7.9 UA 信息中包含 UCWEB 字段 63 | m = ua.match(/(UCWEB)([\d.]+)/); 64 | info.browser.shell = 'ucweb'; 65 | info.browser.version = m[2]; 66 | } else if ((/UC/i).test(ua)) { 67 | // UCWeb 8.x UA 信息中包含 UC 字段 68 | // 确认 8.6、8.7 69 | info.browser.shell = 'ucweb'; 70 | info.browser.version = '8.x'; 71 | } 72 | 73 | if (info.browser.shell === 'ucweb') { 74 | // UC 浏览器急速模式 75 | // 目前只有 Android 平台国内版 UCWeb 9.0 可以判断是否为急速模式,UA 中包含 UCWEB/2.0 字段即为急速模式。 76 | if ((/UCWEB\/2\.0/i).test(ua)) { 77 | info.browser.module = 'swift'; 78 | } 79 | } 80 | 81 | if (info.browser.version) { 82 | info.browser.version = parseFloat(info.browser.version, 10); 83 | } 84 | 85 | return info; 86 | }()); -------------------------------------------------------------------------------- /example/dropdown.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Squirrel Dropdown 11 | 12 | 13 |
14 | 26 |
27 |

Example

28 | 29 |
30 | 31 | 36 |
37 | 38 |
39 | 40 | 45 |
46 | 47 |
48 | 49 |
50 | 51 | 56 |
57 |
58 |
59 | Top 60 | 61 | 62 | 63 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/styles/grid.less: -------------------------------------------------------------------------------- 1 | // 2 | // Grid 3 | // @desc: 栅格系统 4 | // --------------------------------------------------------------------------- 5 | 6 | .col-tb-visible{ display:none;} 7 | 8 | //.row{.clearfix();} 9 | 10 | 11 | // Size 12 | // @desc: 生成栏,参照 bootstrap Grid 13 | // -------------------------------------------- 14 | .make-grid-columns(); 15 | 16 | .make-grid-columns() { 17 | .col(@index) when (@index = 1) { // initial 18 | @item: ~".col-mb-@{index}, .col-tb-@{index}"; 19 | .col((@index + 1), @item); 20 | } 21 | .col(@index, @list) when (@index =< @grid-columns) { // general; "=<" isn't a typo 22 | @item: ~".col-mb-@{index}, .col-tb-@{index}"; 23 | .col((@index + 1), ~"@{list}, @{item}"); 24 | } 25 | .col(@index, @list) when (@index > @grid-columns) { // terminal 26 | @{list} { 27 | position: relative; 28 | // Prevent columns from collapsing when empty 29 | min-height: 1px; 30 | // Inner gutter via padding 31 | padding-left: (@grid-gutter-width / 2); 32 | padding-right: (@grid-gutter-width / 2); 33 | } 34 | } 35 | .col(1); // kickstart it 36 | } 37 | 38 | .float-grid-columns(@class) { 39 | .col(@index) when (@index = 1) { // initial 40 | @item: ~".col-@{class}-@{index}"; 41 | .col((@index + 1), @item); 42 | } 43 | .col(@index, @list) when (@index =< @grid-columns) { // general 44 | @item: ~".col-@{class}-@{index}"; 45 | .col((@index + 1), ~"@{list}, @{item}"); 46 | } 47 | .col(@index, @list) when (@index > @grid-columns) { // terminal 48 | @{list} { 49 | float: left; 50 | } 51 | } 52 | .col(1); // kickstart it 53 | } 54 | 55 | .calc-grid-column(@index, @class, @type) when (@type = width) and (@index > 0) { 56 | .col-@{class}-@{index} { 57 | width: percentage((@index / @grid-columns)); 58 | } 59 | } 60 | .calc-grid-column(@index, @class, @type) when (@type = push) { 61 | .col-@{class}-push-@{index} { 62 | left: percentage((@index / @grid-columns)); 63 | } 64 | } 65 | .calc-grid-column(@index, @class, @type) when (@type = pull) { 66 | .col-@{class}-pull-@{index} { 67 | right: percentage((@index / @grid-columns)); 68 | } 69 | } 70 | .calc-grid-column(@index, @class, @type) when (@type = offset) { 71 | .col-@{class}-offset-@{index} { 72 | margin-left: percentage((@index / @grid-columns)); 73 | } 74 | } 75 | 76 | // Basic looping in LESS 77 | .loop-grid-columns(@index, @class, @type) when (@index >= 0) { 78 | .calc-grid-column(@index, @class, @type); 79 | // next iteration 80 | .loop-grid-columns((@index - 1), @class, @type); 81 | } 82 | 83 | // Create grid for specific class 84 | .make-grid(@class) { 85 | .float-grid-columns(@class); 86 | .loop-grid-columns(@grid-columns, @class, width); 87 | .loop-grid-columns(@grid-columns, @class, pull); 88 | .loop-grid-columns(@grid-columns, @class, push); 89 | .loop-grid-columns(@grid-columns, @class, offset); 90 | } 91 | 92 | 93 | @media @mobile-screen { 94 | .make-grid(mb); 95 | } 96 | 97 | @media @table-screen { 98 | .make-grid(tb); 99 | .col-tb-visible{ display:block;} 100 | } -------------------------------------------------------------------------------- /src/styles/themes/default-theme.less: -------------------------------------------------------------------------------- 1 | // 2 | // Material UI 3 | // --------------------------------------------------------------------------- 4 | 5 | @ns: ~"sq-"; 6 | @space-normal: 16px; 7 | 8 | 9 | @white: #fff; 10 | @font-grey: #757575; 11 | @base-blue: #3f51b5; 12 | @border-color-grey: #e0e0e0; 13 | 14 | 15 | @shadow-normal: 0 2px 5px rgba(0,0,0,0.26); 16 | @shadow-depth-2: 0 1px 6px rgba(0, 0, 0, 0.36); 17 | 18 | hr{ display:block; height:1px; background:@border-color-grey; margin:8px 0; border:none; clear:both;} 19 | 20 | .@{ns}bar{ 21 | height:56px; padding:0; font-size:20px; background:@base-blue; color:@white; line-height:56px; 22 | // 超过 768px: height:64px; font-size:24px; 23 | 24 | box-shadow:@shadow-normal; 25 | 26 | &.nav{ 27 | & > .title{ text-align:left; font-size:20px; margin:0; padding:0 16px;} 28 | & > .extend{} 29 | .sq-btn{ 30 | width:48px; height:48px; padding:0; margin:4px; color:@white; line-height:48px; 31 | } 32 | } 33 | } 34 | 35 | .@{ns}list{ 36 | .group-hd{ font-size:1.6rem; color:@font-grey;} 37 | .list-item{ 38 | padding:(@space-normal - 1) @space-normal; 39 | &:after{ content:''; display:block; position:absolute; left:72px; right:0; bottom:-1px; height:1px; border-bottom:1px solid transparent;} 40 | &.divider{ border-color:@border-color-grey;} //等屏宽分隔线 Full-bleed dividers 41 | &.divider-inset:after{ border-color:@border-color-grey;} 42 | & > .icon:last-child{ width:auto;} 43 | } 44 | &.divider{ 45 | & > .list-item{ border-color:@border-color-grey;} 46 | } 47 | &.divider-inset{ 48 | & > .list-item:after{ border-color:@border-color-grey;} 49 | } 50 | &.single-line{ 51 | & > .list-item{ padding:7px @space-normal;} 52 | } 53 | 54 | // 保证文字以 75px 对齐 55 | .sq-icon{ width:56px;} 56 | .icon{ 57 | width:56px; height:auto; 58 | img{ width:40px; border-radius:999rem;} 59 | } 60 | 61 | &.text{ 62 | .list-item{ 63 | & > a{ padding:(@space-normal - 2) @space-normal (@space-normal - 3);} 64 | } 65 | .sq-icon{ 66 | width:56px; margin:0; text-align:left; 67 | &.sq-fr{ width:auto;} 68 | } 69 | } 70 | 71 | &.media{ 72 | .title{ line-height:1;} 73 | .desc{ margin-left:0; line-height:1;} 74 | .digest{ color:@font-grey;} 75 | .extend{ 76 | .sq-icon{ width:auto;} 77 | } 78 | } 79 | } 80 | 81 | .@{ns}btn{ 82 | padding:9px 8px; // height:36px; 83 | &.flat{} 84 | &.raised{ border-radius:2px; box-shadow:@shadow-depth-2;} 85 | &.float-action{} 86 | } 87 | 88 | .@{ns}card{ 89 | margin:@space-normal / 2; box-shadow:@shadow-depth-2; border-radius:2px; 90 | & > .hd{ margin:0;} 91 | & > .bd{ 92 | padding:@space-normal; 93 | img{ width:100%;} 94 | } 95 | & > .hd + .bd{ padding-top:0;} 96 | & > .action{ 97 | padding:@space-normal / 2; border-top:1px solid @border-color-grey; 98 | } 99 | 100 | .card-title{ margin-bottom:@space-normal - 2;} 101 | 102 | .gallery-item{ 103 | margin:0; padding:0; 104 | } 105 | } -------------------------------------------------------------------------------- /src/styles/list.less: -------------------------------------------------------------------------------- 1 | // 2 | // List 3 | // @desc: 列表 4 | // --------------------------------------------------------------------------- 5 | 6 | .@{ns}list{ 7 | 8 | .group-hd, 9 | .list-item{ 10 | display:block; position:relative; padding:@list-padding; border-top:1px solid transparent; border-bottom:1px solid transparent; font-size:@list-font-size; .clearfix(); 11 | line-height:@list-line-height; // 保证中英文文本水平对齐 12 | & + .group-hd, 13 | & + .list-item{ margin-top:-1px;} 14 | &.divider{ border-bottom-color:@list-multi-border-color;} //等屏宽分隔线 Full-bleed dividers 15 | } 16 | &.divider{ 17 | & > .list-item{ border-color:@list-multi-border-color;} 18 | } 19 | .icon, 20 | .thumb{ 21 | display:block; position:relative; 22 | img{ display:block; width:100%;} 23 | .sq-badge{ position:absolute; top:@list-badge-pos-top; left:@list-badge-pos-left;} 24 | } 25 | //.icon{ width:@list-icon-width; height:@list-icon-height;} 26 | .title{ display:block; margin-bottom:@list-inside-space;} 27 | .overlay{ .overlay(); text-indent:-999em; z-index:@list-overlay-zindex; cursor:pointer;} 28 | .align-middle{ display:flex; align-items:center;} 29 | 30 | 31 | // Text 32 | // @desc: 文字列表,通常只包含文字、徽章或少量控件 33 | // -------------------------------------------- 34 | &.text{ 35 | .list-item{ 36 | padding:0; 37 | & > a{ display:block; padding:@list-padding;} 38 | } 39 | .title{ font-size:@content-font-size;} 40 | .data{ display:inline-block; float:right; width:80px; margin-left:1rem;} 41 | .sq-badge{ margin:-2px 0; margin-left:1rem;} // 修正 sq-badge 过高将 list-item 撑开的问题 42 | //.sq-icon:not(.sq-fr){ display:inline-block; width:48px; margin:-3px 0 -3px -15px; text-align:center;} 43 | .sq-icon{ 44 | display:inline-block; margin-right:12px; 45 | &.sq-fr{ margin-right:0;} 46 | } 47 | } 48 | 49 | 50 | // Media 51 | // @desc: 媒体列表,布局复杂、控件丰富的列表 52 | // -------------------------------------------- 53 | &.media{ 54 | .list-item{ display:flex;} 55 | .desc{ 56 | flex:1; margin:0 @list-padding; 57 | .title{ font-size:@title-font-size;} 58 | .digest{ margin-bottom:@list-inside-space; font-size:@content-font-size;} 59 | :last-child{ margin-bottom:0;} 60 | } 61 | // 消除多余边距 62 | :first-child{ margin-left:0;} 63 | :last-child{ margin-right:0;} 64 | // 扩展内容,通常放置按钮、Icon 65 | .extend{ 66 | position:relative; z-index:@list-overlay-zindex + 1; // 高于 .overlay 67 | .sq-btn{ 68 | float:left; 69 | & + .sq-btn{ 70 | margin-left:@list-inside-space; 71 | &.block{ margin-left:0; margin-top:@list-inside-space;} 72 | } 73 | } 74 | } 75 | // 一行多列显示优化 76 | &[class*="@{ns}col-"]{ 77 | border-bottom:1px solid @list-multi-border-color; border-left:1px solid @list-multi-border-color; 78 | .list-item{ 79 | margin:0 0 0 -1px; border-bottom:none; border-left:1px solid @list-multi-border-color; border-right:1px solid @list-multi-border-color; 80 | } 81 | } 82 | } 83 | 84 | 85 | // Tile 86 | // @desc:平布列表 87 | // -------------------------------------------- 88 | &.tile{ 89 | .list-item{ float:left; margin:0; padding:@list-tile-padding; text-align:center; border:none;} 90 | .icon{ margin:0 auto @list-inside-space;} 91 | } 92 | } -------------------------------------------------------------------------------- /example/gallery.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Squirrel Gallery 11 | 12 | 13 |
14 | 26 |
27 |

Gallery

28 | 53 | 54 |
55 | 56 | 81 |
82 |
83 | Top 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /example/screenshot.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Squirrel Screenshot 11 | 42 | 43 | 44 |
45 | 57 |
58 |

Screenshot

59 |
60 |
61 |
62 |
63 |
64 | 65 |
66 |
67 |
    68 |
  • 69 | 1 70 |
  • 71 |
  • 72 | 2 73 |
  • 74 |
  • 75 | 3 76 |
  • 77 |
  • 78 | 4 79 |
  • 80 |
  • 81 | 5 82 |
  • 83 |
84 |
85 | 94 |
95 |
96 |
97 | 98 | 99 | 100 | 108 | 109 | -------------------------------------------------------------------------------- /src/styles/bar.less: -------------------------------------------------------------------------------- 1 | // 2 | // Bar 3 | // @desc: 栏,用于告知用户其所在位置的情境信息,以及帮助用户浏览或执行操作的控件, 4 | // 包含导航栏、工具栏、选项栏等。 5 | // --------------------------------------------------------------------------- 6 | 7 | .@{ns}bar{ 8 | height:@bar-normal-height; padding:@bar-toolbar-padding; 9 | .flex{ display:flex;} 10 | .@{ns}btn{ display:block; z-index:1;} // 兼容:Andorid 4.1 无法实现 flexbox 相关属性 11 | 12 | 13 | // Tool bar 14 | // @desc: 工具栏,能实现在不同信息层级结构之间的导航 15 | // -------------------------------------------- 16 | &.toolbar{ 17 | // flexbox 18 | // http://css-tricks.com/snippets/css/a-guide-to-flexbox/ 19 | position:relative; display:flex; align-items:center; justify-content:space-between; z-index:@bar-toolbar-index; 20 | 21 | // 对齐方式 22 | &.align-start, 23 | .align-start{ justify-content:flex-start;} 24 | &.align-end, 25 | .align-end{ justify-content:flex-end;} 26 | } 27 | 28 | 29 | // Navigation bar 30 | // @desc: 导航栏,能实现在不同信息层级结构之间的导航 31 | // @extend: .sq-bar.toolbar 32 | // -------------------------------------------- 33 | &.nav:extend(.toolbar all){ 34 | // 标题栏 35 | & > .title{ flex:1; height:@bar-normal-height; margin:@bar-nav-extend-margin; font-size:@bar-title-font-size; line-height:@bar-normal-height; text-align:center; overflow:hidden;} 36 | // 扩展区域 37 | & > .extend{ 38 | display:flex; align-items:center; justify-content:space-between; flex:1; height:@bar-normal-height; margin:@bar-nav-extend-margin; 39 | .@{ns}btn-group{ width:100%;} 40 | } 41 | } 42 | 43 | 44 | // Pagination bar 45 | // @desc: 分页栏 46 | // @extend: .sq-bar.toolbar 47 | // -------------------------------------------- 48 | &.pagination:extend(.toolbar all){ 49 | // 翻页 50 | select{ min-height:@bar-normal-height - 10; line-height:1;} 51 | & > .pagination-select{ .overlay(0,80px,0,80px); font-size:@bar-pagination-font-size; text-align:center; line-height:@bar-normal-height;} 52 | .disabled{ pointer-events:none; opacity:.4; cursor:not-allowed;} 53 | } 54 | 55 | 56 | // Tabs 57 | // @desc: 选项栏 58 | // -------------------------------------------- 59 | &.nav-tabs{ 60 | font-size:@bar-tab-font-size; 61 | 62 | & > a{ display:flex; align-items:center; justify-content:space-between; position:relative; height:@bar-normal-height; text-align:center;} 63 | // 嵌套一层 span 是为了达到垂直居中的效果 64 | span{ display:block; flex:1;} 65 | 66 | // 带图标样式 67 | &.icons{ 68 | & > a{ font-size:1.2rem;} 69 | .@{ns}icon{ display:block;} 70 | .@{ns}badge{ left:60%; right:auto;} 71 | } 72 | 73 | .@{ns}badge{ 74 | position:absolute; top:-2px; right:10%; padding:4px; min-width:20px; border-radius:@border-radius-circle; line-height:1; z-index:1; 75 | } 76 | } 77 | 78 | 79 | // Search 80 | // @desc: 搜索栏 81 | // @extend: .sq-bar.toolbar 82 | // -------------------------------------------- 83 | &.search:extend(.toolbar all){ 84 | padding:@bar-search-padding; 85 | 86 | [type="text"], 87 | [type="search"], 88 | [type="submit"], 89 | [type="button"]{ border:none; outline:none;} 90 | 91 | [type="text"], 92 | [type="search"]{ 93 | display:block; flex:1; height:@bar-search-height; line-height:@bar-search-height; 94 | & + .@{ns}btn{ margin-left:@bar-search-btn-space;} 95 | } 96 | 97 | form{ display:flex; width:100%;} 98 | 99 | .label{ 100 | position:relative; flex:1; background:@bar-search-label-bg; 101 | &.icon-search{ 102 | line-height:@bar-search-height; 103 | &::before{ position:relative; top:1px; padding:0 8px; content:@bar-search-icon;} 104 | } 105 | } 106 | .@{ns}btn{ max-height:@bar-search-height + 2; overflow:hidden;} 107 | 108 | /*.clear{ 109 | display:none; position:absolute; top:0; right:0; width:30px; height:28px; cursor:pointer; 110 | &::after{ content:"×"; position:absolute; top:5px; left:6px; width:18px; height:18px; background:#ccc; border-radius:@border-radius-circle; text-align:center; line-height:16px; color:#fff;} 111 | }*/ 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/styles/form.less: -------------------------------------------------------------------------------- 1 | // 2 | // Form 3 | // @desc: 表单元素 4 | // --------------------------------------------------------------------------- 5 | 6 | 7 | // Normalize 8 | // @desc: 标准化 9 | // -------------------------------------------- 10 | label{ display:block; margin:@form-label-margin;} 11 | input[type="checkbox"], 12 | input[type="radio"]{ margin:@form-radio-checkbox-margin; line-height:normal;} 13 | input[type="search"]{ box-sizing:border-box;} // 修复 * 无法匹配 search 的问题 14 | input[type="file"]{ display: block;} 15 | 16 | /*select{ box-shadow:none; outline:none;} 17 | select[multiple], 18 | select[size] { height: auto;}*/ 19 | 20 | 21 | // Custom 22 | // @desc: 定制控件 23 | // -------------------------------------------- 24 | .@{ns}form{ 25 | input[type="text"], 26 | input[type="file"], 27 | input[type="search"], 28 | input[type="password"], 29 | input[type="number"], 30 | input[type="email"], 31 | input[type="tel"], 32 | textarea, 33 | select{ 34 | width:100%; height:@form-input-height; padding:@form-input-padding; border:1px solid @form-input-border-color; margin:@form-input-margin; border-radius:@form-input-border-radius; outline:none; line-height:1; 35 | &::-webkit-input-placeholder{ color:@form-input-placeholder;} 36 | } 37 | textarea{ height:auto;} 38 | 39 | .form-item{ margin:@form-item-margin; clear:both; user-select:none;} 40 | .help-block{ display:block; margin-bottom:10px; color:#999; font-size:1.2rem; clear:both;} 41 | .radio, 42 | .checkbox{ 43 | // padding-left 为 21 px 是为了修复 radio 显示不全的问题。 44 | min-width:20px; margin-bottom:1.5rem; padding:0 20px 0 21px; 45 | input[type="radio"], 46 | input[type="checkbox"], 47 | .sq-checkbox, 48 | .sq-radio{ margin-left:-20px;} 49 | &.inline{ display:inline-block; float:left;} 50 | } 51 | 52 | 53 | // inline form 54 | // @desc: 行内表单布局 55 | // -------------------------------------------- 56 | &.inline{ 57 | display:flex; align-items:center; justify-content:space-between; overflow:hidden; 58 | select, 59 | input{ margin-bottom:0;} 60 | label{ margin:0 .5rem; padding-right:0; line-height:1;} 61 | input[type="submit"], 62 | input[type="button"]{ 63 | width:100%; 64 | } 65 | .form-item{ 66 | display:block; flex:1; padding-right:4px; margin-bottom:0; 67 | &:last-of-type{ padding-right:0;} 68 | } 69 | } 70 | 71 | // 水平表单布局 72 | &.horizontal{ 73 | .form-item{ 74 | //.display-box; .box-orient(horizontal); .box-align(center); 75 | display:flex; align-items:center; justify-content:space-between; 76 | &.align-top{ 77 | //.box-align(start); 78 | justify-content:flex-start; 79 | label:first-of-type{ padding-top:4px;} 80 | } 81 | } 82 | 83 | label{ min-width:3em;} 84 | 85 | input[type="text"], 86 | input[type="search"], 87 | input[type="password"], 88 | input[type="number"], 89 | input[type="email"], 90 | input[type="tel"], 91 | textarea, 92 | select{ 93 | display:block; width:100%; flex:1; margin-right:.5rem; 94 | &+input{ margin-left:.5rem;} 95 | &:last-of-type{ margin-right:0;} 96 | } 97 | 98 | .wd-4, 99 | .wd-6, 100 | .wd-8{ width:9em; text-align:right;} 101 | .wd-4{ width:5em;} 102 | .wd-6{ width:7em;} 103 | } 104 | } 105 | 106 | 107 | // Form 108 | // @desc: 优化表单 109 | // -------------------------------------------- 110 | .@{ns}checkbox, 111 | .@{ns}radio{ 112 | display:inline-block; position:relative; width:21px; height:21px; margin-right:.7rem; text-align:center; vertical-align:middle; overflow:hidden; 113 | & > span{ .overlay(); background:#fff; border:2px solid #ccc; border-radius:3px; text-indent:-999em;} 114 | input[type=checkbox], 115 | input[type=radio]{ 116 | visibility: hidden; 117 | } 118 | input[type=checkbox]:checked + span { 119 | background:rgba(76,176,80,.9); border-color:#4CAF50; color:#fff; text-indent:0; 120 | } 121 | input[type=radio]:checked + span{ 122 | background:rgba(76,176,80,.6); border-color:#4CAF50; color:#fff; text-indent:0; 123 | } 124 | } 125 | .@{ns}radio{ 126 | & > span{ border-radius:@border-radius-circle;} 127 | } -------------------------------------------------------------------------------- /doc_tpl/documentation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Squirrel 文档 11 | 14 | 15 | 16 | 20 |
21 |
22 | 37 |
38 | 70 | 71 |
72 | 73 |
74 | 75 | 76 |
77 | 78 | -------------------------------------------------------------------------------- /src/scripts/core/core.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file SQ.core 3 | * @version 1.0.0 4 | */ 5 | /*global SQ*/ 6 | window.SQ = { 7 | /** 8 | * 命名空间方法 9 | * @method 10 | * @name SQ.core.namespace 11 | * @param {string} nameSpaceString 命名空间字符串 12 | * @example 13 | * SQ.core.namespace('SQ.modules.module2'); 14 | */ 15 | namespace: function (nameSpaceString) { 16 | 'use strict'; 17 | var parts = nameSpaceString.split('.'); 18 | var parent = SQ; 19 | var i; 20 | if (parts[0] === 'SQ') { 21 | parts = parts.slice(1); 22 | } else { 23 | return false; 24 | } 25 | for (i = 0; i < parts.length; i += 1) { 26 | if (typeof parent[parts[i]]) { 27 | parent[parts[i]] = {}; 28 | } 29 | parent = parent[parts[i]]; 30 | } 31 | return parent; 32 | }, 33 | /** 34 | * 判断对象类型 35 | * @example 36 | * SQ.core.isString(str); 37 | */ 38 | isString: function (str) { 39 | 'use strict'; 40 | return Object.prototype.toString.call(str) === '[object String]'; 41 | }, 42 | isArray: function (arr) { 43 | 'use strict'; 44 | return Object.prototype.toString.call(arr) === '[object Array]'; 45 | }, 46 | isNumber: function (num) { 47 | 'use strict'; 48 | return Object.prototype.toString.call(num) === '[object Number]'; 49 | }, 50 | isBoolean: function (bool) { 51 | 'use strict'; 52 | return Object.prototype.toString.call(bool) === '[object Boolean]'; 53 | }, 54 | isNull: function (nullObj) { 55 | 'use strict'; 56 | return Object.prototype.toString.call(nullObj) === '[object Null]'; 57 | }, 58 | isUndefined: function (undefinedObj) { 59 | 'use strict'; 60 | return Object.prototype.toString.call(undefinedObj) === '[object Undefined]'; 61 | }, 62 | isFunction: function (fun) { 63 | 'use strict'; 64 | return Object.prototype.toString.call(fun) === '[object Function]'; 65 | }, 66 | isObject: function (obj) { 67 | 'use strict'; 68 | return Object.prototype.toString.call(obj) === '[object Object]'; 69 | }, 70 | /** 71 | * isJSON 72 | * 判断是否为 JSON 对象 73 | * @param string 74 | * @returns {boolean} 75 | * @see qatrix.js 76 | */ 77 | // 暂时无法使用 78 | /*isJSON : function (string) { 79 | var rvalidchars = /^[\],:{}\s]*$/; 80 | var rvalidescape = /\\(?:['\\\/bfnrt]|u[\da-fA-F]{4})/g; 81 | var rvalidtokens = /'[^'\\\r\n]*'|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g; 82 | var rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g; 83 | return typeof string === 'string' && $.trim(string) !== '' ? 84 | rvalidchars.test(string 85 | .replace(rvalidescape, '@') 86 | .replace(rvalidtokens, ']') 87 | .replace(rvalidbraces, '')) : 88 | false; 89 | }*/ 90 | /** 91 | * 继承 92 | * @param Child 93 | * @param Parent 94 | */ 95 | extend: function (Child, Parent) { 96 | 'use strict'; 97 | var F = function () { 98 | }; 99 | F.prototype = Parent.prototype; 100 | Child.prototype = new F(); 101 | Child.prototype.constructor = Child; 102 | Child.uber = Parent.prototype; 103 | }, 104 | /** 105 | * 频率控制 返回函数连续调用时,fn 执行频率限定为每多少时间执行一次 106 | * @param fn {function} 需要调用的函数 107 | * @param delay {number} 延迟时间,单位毫秒 108 | * @param immediate {bool} 给 immediate 参数传递 false 绑定的函数先执行,而不是 delay 后后执行。 109 | * @return {function} 实际调用函数 110 | */ 111 | throttle: function (fn, delay, immediate, debounce) { 112 | 'use strict'; 113 | var curr; 114 | var lastCall = 0; 115 | var lastExec = 0; 116 | var timer = null; 117 | var diff; // 时间差 118 | var context; // 上下文 119 | var args; 120 | var exec = function () { 121 | lastExec = curr; 122 | fn.apply(context, args); 123 | }; 124 | return function () { 125 | curr = + new Date(); 126 | context = this; 127 | args = arguments; 128 | diff = curr - (debounce ? lastCall : lastExec) - delay; 129 | clearTimeout(timer); 130 | if (debounce) { 131 | if (immediate) { 132 | timer = setTimeout(exec, delay); 133 | } else if (diff >= 0) { 134 | exec(); 135 | } 136 | } else { 137 | if (diff >= 0) { 138 | exec(); 139 | } else if (immediate) { 140 | timer = setTimeout(exec, -diff); 141 | } 142 | } 143 | lastCall = curr; 144 | }; 145 | }, 146 | /** 147 | * 空闲控制 返回函数连续调用时,空闲时间必须大于或等于 delay,fn 才会执行 148 | * @param fn {function} 要调用的函数 149 | * @param delay {number} 空闲时间 150 | * @param immediate {bool} 给 immediate 参数传递 false 绑定的函数先执行,而不是 delay 后后执行。 151 | * @return {function} 实际调用函数 152 | */ 153 | debounce: function (fn, delay, immediate) { 154 | 'use strict'; 155 | return SQ.throttle(fn, delay, immediate, true); 156 | } 157 | }; -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Squirrel Example 11 | 12 | 13 |
14 | 17 | 129 | 130 |
131 | Top 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /src/scripts/core/store.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file SQ.store 3 | * @version 1.1.0 4 | */ 5 | /*global SQ*/ 6 | SQ.store = { 7 | /** 8 | * Cookie 9 | * @example 10 | * Sq.cookie.set('name', 'value'); // 设置 11 | * Sq.cookie.get('name'); // 读取 12 | * Sq.cookie.del('name'); // 删除 13 | */ 14 | cookie: { 15 | _getValue: function (offset) { 16 | 'use strict'; 17 | var ck = document.cookie; 18 | var endstr = ck.indexOf(';', offset) === -1 ? ck.length : ck.indexOf(';', offset); 19 | return decodeURIComponent(ck.substring(offset, endstr)); 20 | }, 21 | get: function (key) { 22 | 'use strict'; 23 | var me = this; 24 | var ck = document.cookie; 25 | var arg = key + '='; 26 | var argLen = arg.length; 27 | var cookieLen = ck.length; 28 | var i = 0; 29 | while (i < cookieLen) { 30 | var j = i + argLen; 31 | if (ck.substring(i, j) === arg) { 32 | return me._getValue(j); 33 | } 34 | i = ck.indexOf(' ', i) + 1; 35 | if (i === 0) { 36 | break; 37 | } 38 | } 39 | return null; 40 | }, 41 | set: function (key, value) { 42 | 'use strict'; 43 | var expdate = new Date(); 44 | var year = expdate.getFullYear(); 45 | var month = expdate.getMonth(); 46 | var date = expdate.getDate() + 1; 47 | var argv = arguments; 48 | var argc = arguments.length; 49 | //获取更多实参,依次为:有效期、路径、域、加密安全设置 50 | var expires = (argc > 2) ? argv[2] : null; 51 | var path = (argc > 3) ? argv[3] : null; 52 | var domain = (argc > 4) ? argv[4] : null; 53 | var secure = (argc > 5) ? argv[5] : false; 54 | 55 | if (!!expires) { 56 | switch (expires) { 57 | case 'day': 58 | expdate.setYear(year); 59 | expdate.setMonth(month); 60 | expdate.setDate(date); 61 | expdate.setHours(8); // 补 8 小时时差 62 | expdate.setMinutes(0); 63 | expdate.setSeconds(0); 64 | break; 65 | case 'week': 66 | var week = 7 * 24 * 3600 * 1000; 67 | expdate.setTime(expdate.getTime() + week); 68 | break; 69 | default: 70 | expdate.setTime(expdate.getTime() + (expires * 1000 + 8 * 3600 * 1000)); 71 | break; 72 | } 73 | } 74 | 75 | document.cookie = key + '=' + encodeURIComponent(value) + ((expires === null) ? '' : ('; expires=' + expdate.toGMTString())) + 76 | ((path === null) ? '' : ('; path=' + path)) + ((domain === null) ? '' : ('; domain=' + domain)) + 77 | ((secure === true) ? '; secure' : ''); 78 | }, 79 | del: function (key) { 80 | 'use strict'; 81 | var me = this; 82 | var exp = new Date(); 83 | exp.setTime(exp.getTime() - 1); 84 | var cval = me.get(key); 85 | document.cookie = key + '=' + cval + '; expires=' + exp.toGMTString(); 86 | } 87 | }, 88 | /** 89 | * localStorage 90 | */ 91 | localStorage: { 92 | hasLoaclStorage: (function () { 93 | 'use strict'; 94 | if(('localStorage' in window) && window.localStorage !== null) { 95 | return true; 96 | } 97 | }()), 98 | // expires 过期时间,单位 min 99 | get: function (key, expires) { 100 | 'use strict'; 101 | var me = this; 102 | var now = new Date().getTime(); 103 | var localData; 104 | var time; 105 | var dataStore; 106 | 107 | if (!key || !me.hasLoaclStorage) { 108 | return; 109 | } 110 | 111 | localData = JSON.parse(localStorage.getItem(key)); 112 | 113 | if (localData) { 114 | time = localData.time; 115 | dataStore = localData.dataStore; 116 | 117 | // 填写了 expires 过期时间 118 | if (expires) { 119 | dataStore = parseInt(expires, 10) * 1000 * 60 > (now - parseInt(time, 10)) ? dataStore : false; 120 | } 121 | return dataStore; 122 | } 123 | }, 124 | set: function (key, value) { 125 | 'use strict'; 126 | var me = this; 127 | var ds = {}; 128 | var now = new Date().getTime(); 129 | if (!key || !value || !me.hasLoaclStorage) { 130 | return; 131 | } 132 | 133 | ds.dataStore = value; 134 | ds.time = now; 135 | ds = JSON.stringify(ds); 136 | 137 | localStorage.setItem(key, ds); 138 | }, 139 | del: function (key) { 140 | 'use strict'; 141 | var me = this; 142 | if (!key || !me.hasLoaclStorage) { 143 | return; 144 | } 145 | localStorage.removeItem(key); 146 | }, 147 | clearAll: function () { 148 | 'use strict'; 149 | var me = this; 150 | if (!me.hasLoaclStorage) { 151 | return; 152 | } 153 | localStorage.clear(); 154 | } 155 | } 156 | }; 157 | -------------------------------------------------------------------------------- /src/scripts/dropdown.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file SQ.Dropdown 按钮插件 3 | * @version 0.6.0 4 | */ 5 | 6 | /** 7 | * @changelog 8 | * 0.6.6 * 更名为 Dropdown 9 | * 0.5.0 * 重写插件,调用方式改为 $. 链式调用。 10 | * 0.2.0 * 重写 menu 模式代码,独立 button.js 为插件 11 | * 0.1.2 * 修复 jshint 问题 12 | * 0.1.1 + 新增 menu 交互模式 13 | * 0.0.1 + 新建 14 | */ 15 | /*global $, SQ, console, jQuery*/ 16 | (function ($) { 17 | 'use strict'; 18 | /** 19 | * @name Dropdown 20 | * @classdesc 按钮交互插件 21 | * @constructor 22 | * @param {object} config 插件配置(下面的参数为配置项,配置会写入属性) 23 | * @param {string} config.ANIMATE 动画类,例如 .fadeIn 24 | * @param {string} config.EVE_EVENT_TYPE 交互触发方式,默认为 'click' 25 | * @param {string} config.MODE 按钮交互模式,默认为 'menu' 26 | * @example $('.J_dropdown').dropdown({ 27 | ANIMATE: '.fadeIn quick' 28 | }); 29 | */ 30 | 31 | var scope = 'sq-dropdown'; // data-* 后缀 32 | var defaults = { 33 | MODE: 'menu', 34 | EVE_EVENT_TYPE: 'click' 35 | }; 36 | 37 | function Dropdown ( element, options ) { 38 | this.element = element; 39 | this.settings = $.extend( {}, defaults, options ); 40 | this._defaults = defaults; 41 | this.init(); 42 | } 43 | 44 | Dropdown.prototype = { 45 | construtor: 'Dropdown', 46 | init: function () { 47 | var me = this; 48 | var date = new Date().getTime().toString().slice(-4); 49 | me.$element = $(me.element); 50 | me.elementClassName = me.settings.selector.slice(1); // '.style-name' => 'style-name' 51 | 52 | // _documentEvent 判断时会碰到相同 className 的情况,会导致无法隐藏菜单 53 | me.classId = scope + '-id-' + date; 54 | me.$element.addClass(me.classId); 55 | if (me.settings.MODE === 'menu') { 56 | me.menu(); 57 | } 58 | }, 59 | setState: function (state) { 60 | var me = this; 61 | if (state === 'active') { 62 | me.$element.addClass('active'); 63 | } 64 | if (state === 'init') { 65 | me.$element.removeClass('active'); 66 | } 67 | }, 68 | menu: function () { 69 | var me = this; 70 | var $doc = $(document); 71 | var $allButtons = $(me.settings.selector); 72 | var $allMenus = $allButtons.find('.dropdown-content'); 73 | var $menu = me.$element.find('.dropdown-content'); 74 | 75 | me.$element.on(me.settings.EVE_EVENT_TYPE + '.sq.dropdown', function () { 76 | if (!me.$element.hasClass('active')) { 77 | _showMenu(); 78 | } else { 79 | _hideMenu(); 80 | } 81 | }); 82 | 83 | function _showMenu() { 84 | //** reset all menus 85 | $allMenus.hide(); 86 | $allButtons.removeClass('active'); 87 | //** add animate 88 | if (me.settings.ANIMATE) { 89 | var animateClassName = me.settings.ANIMATE.indexOf('.') === 0 ? me.settings.ANIMATE.slice(1) : me.settings.ANIMATE; 90 | $menu.addClass('animated ' + animateClassName); 91 | } 92 | $menu.show(); 93 | me.setState('active'); 94 | $doc.on('click.sq.dropdown', _documentEvent); 95 | } 96 | 97 | function _hideMenu() { 98 | $menu.hide(); 99 | me.setState('init'); 100 | $doc.off('click.sq.dropdown', _documentEvent); 101 | } 102 | 103 | function _documentEvent(e) { 104 | if (!$(e.target).parents('.' + scope).hasClass(me.classId)) { 105 | _hideMenu(); 106 | } 107 | } 108 | } 109 | }; 110 | 111 | $.fn.dropdown = function ( options ) { 112 | var isZepto = typeof Zepto !== 'undefined' ? true : false; 113 | var isJQuery = typeof jQuery !== 'undefined' ? true : false; 114 | var plugin; 115 | 116 | if (SQ.isObject(options)) { 117 | options = options || {}; 118 | // 当使用 $(this).modal({...}) 调用时,无法获取 this.selector 值, 119 | // 所以去手动获取该 DOM 的类名 120 | options.selector = this.selector || '.' + this[0].className.split(' ').join('.'); 121 | } 122 | 123 | this.each(function() { 124 | if (isJQuery) { 125 | if (!$.data(this, scope + 'init')) { 126 | $.data( this, scope + 'init', new Dropdown( this, options ) ); 127 | } 128 | } else if (isZepto) { 129 | if (!$(this).data(scope + 'init')) { 130 | plugin = new Dropdown( this, options ); 131 | $(this).data(scope + 'init', 'initialized'); 132 | } 133 | } 134 | }); 135 | // chain jQuery functions 136 | return this; 137 | }; 138 | 139 | // DATA-API 140 | // =============== 141 | /*$(document).on('click.button.data.api', '[data-'+ scope +']', function () { 142 | var $me = $(this); 143 | var pluginSetting = $me.data(scope); 144 | //console.log(pluginSetting) 145 | $me.button(pluginSetting); 146 | });*/ 147 | /*$(document).ready(function () { 148 | $('[data-'+ scope +']').each(function () { 149 | var $me = $(this); 150 | var pluginSetting = $me.data(scope); 151 | if (SQ.isObject(pluginSetting)) { 152 | $me.button(pluginSetting); 153 | } 154 | }); 155 | });*/ 156 | })($); -------------------------------------------------------------------------------- /example/bar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Squirrel Example Bars 11 | 12 | 13 |
14 | 28 |
29 | 30 |

工具栏

31 | 32 |
33 | Unread(12) 34 | 35 | 39 |
40 |
41 |
42 | 43 | 44 | 45 | 46 |
47 | 48 | 49 |

导航栏

50 | 55 |
56 | 66 | 67 | 68 |

分页栏

69 | 79 | 80 | 81 |

选项栏

82 | 88 |
89 | 95 |

搜索栏

96 | 102 |
103 | 109 |
110 | 115 |
116 |
117 | Top 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Squirrel-3 3 | * @version 1.0.1 4 | * @anthor: iinterest.bell@gmail.com 5 | */ 6 | /* jshint strict: false, quotmark: false */ 7 | 8 | /** 9 | * change log 10 | * 1.0.1 * autoprefixer 设置为 'android 4', 'ios 7' 11 | * - 删除 gulp-minify-css 12 | */ 13 | 14 | // Load plugins 15 | var gulp = require('gulp'); 16 | var autoprefixer = require('gulp-autoprefixer'); 17 | var concat = require('gulp-concat'); 18 | var del = require('del'); 19 | var footer = require('gulp-footer'); 20 | var gulpif = require('gulp-if'); 21 | var jshint = require('gulp-jshint'); 22 | var less = require('gulp-less'); 23 | var livereload = require('gulp-livereload'); 24 | var notify = require('gulp-notify'); 25 | var plumber = require('gulp-plumber'); 26 | var rename = require('gulp-rename'); 27 | var replace = require('gulp-replace'); 28 | var uglify = require('gulp-uglify'); 29 | var sourcemaps = require('gulp-sourcemaps'); 30 | 31 | var pkg = require('./package.json'); 32 | var date = new Date(); 33 | pkg.date = date.getFullYear() + '-' + ('00' + (date.getMonth() + 1)).slice(-2) + '-' + 34 | ('00' + date.getDate()).slice(-2) + ' ' + ('00' + date.getHours()).slice(-2) + ':' + 35 | ('00' + date.getMinutes()).slice(-2) + ':' + ('00' + date.getSeconds()).slice(-2); 36 | var timestamp = ['\n/*', 37 | ' <%= pkg.name %>', 38 | ' @version v<%= pkg.version %>', 39 | ' @date <%= pkg.date %>', 40 | ' */'].join(''); 41 | 42 | // Config 43 | var toggle = { 44 | autoprefix: true, 45 | imagemin: false, 46 | jshint: true, 47 | sourcemaps: true 48 | }; 49 | var paths = { 50 | scripts: [ 51 | 'src/scripts/core/*.js', 52 | 'src/scripts/global.js', 53 | 'src/scripts/*.js', 54 | 'src/scripts/plugins/*.js', 55 | 'src/scripts/**/*.js' 56 | ], 57 | styles: 'src/styles/' + pkg.name + '.less', 58 | images: [ 59 | 'src/images/**/*.jpg', 60 | 'src/images/**/*.gif', 61 | 'src/images/**/*.png' 62 | ], 63 | fonts: [ 64 | /*'bower_components/fontawesome/fonts/fontawesome.otf', 65 | 'bower_components/fontawesome/fonts/fontawesome-webfont.eot', 66 | 'bower_components/fontawesome/fonts/fontawesome-webfont.svg',*/ 67 | 'bower_components/fontawesome/fonts/fontawesome-webfont.ttf', 68 | 'bower_components/fontawesome/fonts/fontawesome-webfont.woff' 69 | ], 70 | lib: [ 71 | 'bower_components/jquery/dist/jquery.min.js', 72 | 'bower_components/jquery/dist/jquery.min.map' 73 | ], 74 | watchs: { 75 | scripts: 'src/scripts/**/*.js', 76 | styles: 'src/styles/**/*.less', 77 | templates: 'example/**/*.html', 78 | dist: 'dist/**/*' 79 | } 80 | }; 81 | var dests = { 82 | styles: './dist/css', 83 | scripts: './dist/js', 84 | images: './dist/images', 85 | fonts: './dist/fonts', 86 | maps: '../maps' 87 | }; 88 | var options = { 89 | autoprefix: { 90 | browsers: ['android 4', 'ios 7'] 91 | }, 92 | fileName: pkg.name, 93 | less: {compress: true}, 94 | imagemin: {progressive: true}, 95 | jshint: '.jshintrc', 96 | jshintReporter: 'default', 97 | rename: {suffix: '.min'} 98 | }; 99 | 100 | // Tasks 101 | // 通常情况,以下任务是不能修改的,如果遇到特殊情况需要修改,请注明! 102 | 103 | // Styles 104 | gulp.task('styles', function () { 105 | gulp.src(paths.styles) 106 | .pipe(plumber({errorHandler: notify.onError('Error: <%= error.message %>')})) 107 | .pipe(gulpif(toggle.sourcemaps, sourcemaps.init())) 108 | .pipe(less(options.less)) 109 | .pipe(gulpif(toggle.autoprefix, autoprefixer(options.autoprefix))) 110 | .pipe(rename(options.rename)) 111 | .pipe(footer(timestamp, {pkg: pkg})) 112 | .pipe(gulpif(toggle.sourcemaps, sourcemaps.write(dests.maps))) 113 | .pipe(gulp.dest(dests.styles)); 114 | }); 115 | 116 | // Scripts 117 | gulp.task('scripts', function () { 118 | gulp.src(paths.scripts) 119 | .pipe(plumber({errorHandler: notify.onError('Error: <%= error.message %>')})) 120 | .pipe(gulpif(toggle.sourcemaps, sourcemaps.init())) 121 | .pipe(gulpif(toggle.jshint, jshint(options.jshint))) 122 | .pipe(gulpif(toggle.jshint, jshint.reporter(options.jshintReporter))) 123 | .pipe(concat(options.fileName + '.js')) 124 | .pipe(rename(options.rename)) 125 | .pipe(uglify()) 126 | .pipe(footer(timestamp, {pkg: pkg})) 127 | .pipe(gulpif(toggle.sourcemaps, sourcemaps.write(dests.maps))) 128 | .pipe(gulp.dest(dests.scripts)); 129 | }); 130 | 131 | // Images 132 | gulp.task('images', function () { 133 | gulp.src(paths.images) 134 | .pipe(gulp.dest(dests.images)); 135 | }); 136 | 137 | // Fonts 138 | gulp.task('coypFonts', function () { 139 | gulp.src(paths.fonts) 140 | .pipe(gulp.dest(dests.fonts)); 141 | }); 142 | 143 | // Lib 144 | gulp.task('copyLib', function () { 145 | gulp.src(paths.lib) 146 | .pipe(gulp.dest(dests.scripts)); 147 | }); 148 | 149 | // Clean 150 | gulp.task('clean', function (cb) { 151 | del(['dist', 'build'], cb); 152 | }); 153 | 154 | // Build 155 | gulp.task('build', ['clean'], function () { 156 | gulp.start('styles', 'scripts', 'images', 'coypFonts', 'copyLib'); 157 | }); 158 | 159 | // Default 160 | gulp.task('default', ['build']); 161 | 162 | // Release 发布 163 | gulp.task('release', function (cb) { 164 | // 因为 UAE 不支持 .map 文件类型,所以暂时在发布时去除 sourcemaps 165 | toggle.sourcemaps = false; 166 | gulp.start('build'); 167 | }); 168 | 169 | // Watch 170 | gulp.task('watch', function () { 171 | // .js files 172 | gulp.watch(paths.watchs.scripts, ['scripts']); 173 | // .less files 174 | gulp.watch(paths.watchs.styles, ['styles']); 175 | // images files 176 | gulp.watch(paths.images, ['images']); 177 | // LiveReload server 178 | livereload.listen(); 179 | // Watch any files in dist/, pages/, reload on change 180 | gulp.watch([paths.watchs.dist, paths.watchs.templates]).on('change', livereload.changed); 181 | }); -------------------------------------------------------------------------------- /example/sticky.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Squirrel Sticky 11 | 12 | 13 |
14 | 26 |
27 |

Sticky

28 | 135 |
136 |
137 | Top 138 | 139 | 140 | 141 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /src/scripts/lazyload.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file SQ.LazyLoad 延迟加载插件 3 | * @version 1.0.3 4 | */ 5 | 6 | /** 7 | * @changelog 8 | * 1.0.3 * data-img 修改为 data-original 9 | * 1.0.2 * 修改 scroll 绑定事件,使用新增的节流函数,精简函数。 10 | * 1.0.1 * 增加验证提示,调整了 init 函数。 11 | * 1.0.0 * 重写插件,调用方式改为 $. 链式调用。 12 | * 0.8.1 * 新增 ANIMATE 设置。 13 | * 0.8.0 * 重写 lazylaod 插件,提高整体性能。 14 | * 0.7.0 * 调整滑动阀值 scrollDelay,由 200 调整至 150; 15 | * * 调整可视区的计算方式,由 offset 改为 getBoundingClientRect; 16 | * * 针对 UC 浏览器极速版进行优化,可以在滑动过程中进行加载。 17 | * 0.6.5 * 修复 jshint 问题。 18 | * 0.6.4 * 修复图片加载失败时会导致 error 时间一直被触发的 bug; 19 | * 修复与 loadmore 插件配合使用时,无法替换加载错误的图片。 20 | * 0.6.3 + 新增首屏图片自动加载功能; 21 | * + 新增占位图、占位背景设置。 22 | * 0.6.0 + 首屏图片自动加载。 23 | * 0.5.1 * 完成图片模式的延迟加载功能。 24 | * 0.0.1 + 新建。 25 | */ 26 | /*global $, SQ, console, jQuery */ 27 | (function ($) { 28 | 'use strict'; 29 | /** 30 | * @name LazyLoad 31 | * @classdesc 内容延迟加载 32 | * @constructor 33 | * @param {object} config 插件配置(下面的参数为配置项,配置会写入属性) 34 | * @param {string} config.ANIMATE 动画类,例如 .fadeIn 35 | * @param {string} config.IMG_PLACEHOLDER 占位图片 36 | * @param {string} config.MODE 延迟加载模式,默认为:image(图片模式) 37 | * @param {number} config.NUM_THRESHOLD 灵敏度,数值越大越灵敏,延迟性越小,默认为 200 38 | * 39 | * @example $('.J_lazyload').lazyload({ 40 | ANIMATE: '.fadeIn' 41 | }); 42 | */ 43 | 44 | var scope = 'sq-lazyload'; // data-* 后缀 45 | var defaults = { 46 | MODE: 'image', 47 | NUM_THRESHOLD: 350, 48 | IMG_PLACEHOLDER: '' 49 | }; 50 | 51 | function Lazyload ( element, options ) { 52 | this.element = element; 53 | this.settings = $.extend( {}, defaults, options ); 54 | this._defaults = defaults; 55 | this.init(); 56 | } 57 | 58 | Lazyload.prototype = { 59 | construtor: 'Lazyload', 60 | scrollTimer: 0, // 滑动计时器 61 | scrollDelay: 150, // 滑动阀值 62 | init: function () { 63 | var me = this; 64 | me.$element = $(me.element); 65 | me.elementClassName = me.settings.selector.slice(1); // '.style-name' => 'style-name' 66 | me.$element.attr('src', me.settings.IMG_PLACEHOLDER); 67 | if (me._verify()) { 68 | me._bindLazyEvent(); 69 | me._trigger(); 70 | me._loadImg(); 71 | } 72 | }, 73 | /** 74 | * 验证参数是否合法 75 | * @returns {boolean} 76 | * @private 77 | */ 78 | _verify: function () { 79 | /*if (!this.$element.length) { 80 | console.warn('SQ.lazyload: '+ this.settings.selector +'下未找到'); 81 | return false; 82 | }*/ 83 | return true; 84 | }, 85 | _bindLazyEvent: function () { 86 | var me = this; 87 | // 为延迟加载元素绑定一次性执行事件 88 | me.$element.one('appear', function () { 89 | var img = this; 90 | var $img = $(img); 91 | var src = $img.attr('data-original'); 92 | // 替换 src 操作 93 | if (src) { 94 | $img.addClass('unvisible').attr('src', src).removeAttr('data-original').removeClass(me.elementClassName); 95 | $img.on('load', function () { 96 | // 添加动画 97 | if (me.settings.ANIMATE) { 98 | var animateClassName = me.settings.ANIMATE.indexOf('.') === 0 ? me.settings.ANIMATE.slice(1) : me.settings.ANIMATE; 99 | $img.addClass('animated ' + animateClassName); 100 | } 101 | $img.removeClass('unvisible'); 102 | }); 103 | $img.on('error', function () { 104 | if (me.settings.IMG_PLACEHOLDER) { 105 | $(this).attr('src', me.settings.IMG_PLACEHOLDER).off('error'); 106 | } 107 | }); 108 | } 109 | }); 110 | }, 111 | _trigger: function () { 112 | var me = this; 113 | $(window).on('scroll.bs.lazyload', SQ.throttle(function () { 114 | if (me.settings.MODE === 'image') { 115 | me._loadImg(); 116 | } 117 | }, me.scrollDelay)); 118 | }, 119 | /** 120 | * 判断是否在显示区域 121 | */ 122 | _isInDisplayArea: function (item) { 123 | var me = this; 124 | if (item.getBoundingClientRect()) { 125 | var pos = item.getBoundingClientRect(); 126 | return pos.top > 0 - me.settings.NUM_THRESHOLD && pos.top - me.settings.NUM_THRESHOLD < window.innerHeight; 127 | } else { 128 | var $item = $(item); 129 | var winH = window.innerHeight; 130 | var winOffsetTop = window.pageYOffset; // window Y 轴偏移量 131 | var itemOffsetTop = $item.offset().top; 132 | // itemOffsetTop >= winOffsetTop 只加载可视区域下方的内容 133 | // winOffsetTop + winH + me.settings.NUM_THRESHOLD 加载可视区域下方一屏内的内容 134 | return itemOffsetTop >= winOffsetTop && itemOffsetTop <= winOffsetTop + winH + me.settings.NUM_THRESHOLD; 135 | } 136 | }, 137 | _loadImg: function () { 138 | var me = this; 139 | if (me.settings.IMG_PLACEHOLDER && me.$element.hasClass(me.elementClassName)) { 140 | //me.$element.attr('src', me.settings.IMG_PLACEHOLDER); 141 | me.$element.on('error', function () { 142 | $(this).attr('src', me.settings.IMG_PLACEHOLDER).off('error'); 143 | }); 144 | } 145 | if (me._isInDisplayArea(me.$element.get(0))) { 146 | me.$element.trigger('appear'); 147 | } 148 | } 149 | }; 150 | 151 | $.fn.lazyload = function ( options ) { 152 | var isZepto = typeof Zepto !== 'undefined' ? true : false; 153 | var isJQuery = typeof jQuery !== 'undefined' ? true : false; 154 | var plugin; 155 | 156 | options = options || {}; 157 | options.selector = this.selector; 158 | 159 | /*if (!this.length) { 160 | console.warn('SQ.lazyload: 未找到'+ this.selector +'元素'); 161 | }*/ 162 | 163 | this.each(function() { 164 | if (isJQuery) { 165 | if ( !$.data( this, scope ) ) { 166 | $.data( this, scope, new Lazyload( this, options ) ); 167 | } 168 | } else if (isZepto) { 169 | if (!$(this).data(scope)) { 170 | plugin = new Lazyload( this, options ); 171 | $(this).data(scope, 'initialized'); 172 | } 173 | } 174 | }); 175 | // chain jQuery functions 176 | return this; 177 | }; 178 | 179 | })($); -------------------------------------------------------------------------------- /example/button.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Squirrel Example Button 11 | 12 | 13 |
14 | 29 |
30 |

按钮

31 |
32 | Anchor 33 |
Div
34 | 35 |
36 | 37 | 38 | 39 |

尺寸

40 |
41 | 42 | 43 |
44 |
45 | 46 | 47 |
48 |
49 | 50 | 51 |
52 |
53 | 54 | 55 |
56 |
57 | 58 |
59 |
60 | 大型块级按钮 61 |
62 | 63 | 64 | 65 |

禁用

66 |
67 | 68 | 按钮 69 |
70 | 71 | 72 | 73 |

按钮组

74 |
75 | 80 |
81 | 86 |
87 |
88 |
89 | 94 |
95 | 96 | 97 | 98 |

图标

99 |
100 | 101 | 102 |
103 |
104 | Button 105 | Button 106 | Button 107 |
108 |
109 | Button 110 | Button 111 |
112 |
113 | Button 114 | Button 115 | Button 116 |
117 |
118 | Button 119 | Button 120 | Button 121 |
122 | 123 | 124 | 125 |

样式

126 |
127 | Button 128 | Button 129 | Button 130 |
131 |
132 | Button 133 | Button 134 | Button 135 |
136 |
137 | 138 | Button 139 |
140 |
141 |
142 | Top 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /example/material.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Squirrel Material 10 | 11 | 12 |
13 | 25 | 26 |
27 |
28 |

Welcome back

29 |

It's been a while.
Read and new books lately?

30 |
31 |
32 | NO 33 | YES 34 |
35 |
36 | 37 |
38 |
39 |
  • 40 | 41 |
    42 |

    Title

    43 |

    Subhead

    44 |
    45 |
  • 46 |
    47 | 55 |
    56 |

    Each of these cards contains a unique data set: 57 | a checklist with an action, a note with an action, a note with a photo.

    58 |
    59 |
    60 | FREE SAMPLE 61 | REVIEW 62 |
    63 |
    64 | 65 |

    单行文本

    66 |
    67 | 79 |
    80 | 81 |
    82 | 101 |
    102 | 103 |

    多行文本

    104 |
    105 | 132 | 159 |
    160 | 161 | 162 | 163 | 172 | 173 |
    174 | 175 | 176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /example/lazyload.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Squirrel Lazyload 11 | 12 | 13 |
    14 | 26 |
    27 |

    Lazyload

    28 | 135 |
    136 |
    137 | Top 138 | 139 | 140 | 141 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /src/styles/variables.less: -------------------------------------------------------------------------------- 1 | // 2 | // Global variables 3 | // 全局变量 4 | // --------------------------------------------------------------------------- 5 | 6 | 7 | // =========================================================================== 8 | // Common 9 | // =========================================================================== 10 | 11 | 12 | // 基本设置 Base Setting 13 | // -------------------------------------------- 14 | @ns: ~"sq-"; 15 | //** 圆角 Radius 16 | @border-radius-circle: 999rem; 17 | @border-radius-base: 3px; 18 | //** 颜色 color 19 | @black: #333333; 20 | @white: #ffffff; 21 | //** 其他 Other 22 | @shardow-normal: 0 1px 6px rgba(0,0,0,.12); 23 | 24 | 25 | // 文字设置 Font 26 | // -------------------------------------------- 27 | @font-family: "Segoe UI", Helvetica, "Lucida Grande", Arial, "Microsoft YaHei", FreeSans, Arimo, "Droid Sans","wenquanyi micro hei","Hiragino Sans GB", "Hiragino Sans GB W3", Arial, sans-serif; 28 | @default-font-color: @black; 29 | @default-font-size: 1.6rem; // 16px 30 | @default-line-height: 1.428571429; // @see bootstrap 31 | @head-font-size: 1.8rem; // 主导航文字 32 | @title-font-size: 1.6rem; // 标题文字 33 | @content-font-size: 1.4rem; // 内容文字 34 | @form-base-font-size: 1.4rem; // 表单文字 35 | @description-font-size: 1.2rem; // 描述文字 36 | @badge-font-size: 1.2rem; // 徽标文字 37 | 38 | 39 | // 媒体查询 Media queries 40 | // -------------------------------------------- 41 | @screen: ~"only screen"; 42 | @landscape: ~"(orientation: landscape)"; 43 | @portrait: ~"(orientation: portrait)"; 44 | //** Mobile size 45 | @mobile-max-size: 640px; // 0 - 640px 46 | @mobile-landscape-size: ~"@{landscape}"; 47 | @mobile-portrait-size: ~"@{portrait}"; 48 | //** Table size 49 | @table-min-size: 768px; // 768 - ∞ 50 | @table-landscape-size: ~"@{landscape}"; 51 | @table-portrait-size: ~"@{portrait}"; 52 | //** Mobile screen 53 | @mobile-screen: ~"@{screen}"; 54 | @mobile-screen-landscape: ~"@{screen} and (orientation: landscape)"; 55 | @mobile-screen-portrait: ~"@{screen} and (orientation: portrait)"; 56 | //** Table screen 57 | @table-screen: ~"@{screen} and (min-width: @{table-min-size})"; 58 | @table-screen-landscape: ~"@{screen} and (min-width: @{table-min-size}) and (@{table-landscape-size})"; 59 | @table-screen-portrait: ~"@{screen} and (min-width: @{table-min-size}) and (@{table-portrait-size})"; 60 | 61 | 62 | // =========================================================================== 63 | // CSS 64 | // =========================================================================== 65 | 66 | 67 | // 按钮 Button 68 | // -------------------------------------------- 69 | @btn-border-radius: @border-radius-base; 70 | //** normal (sq-btn height = 9*2 + 16 + 2 = 36px) 71 | @btn-padding-normal: 9px 1rem; 72 | //** large 73 | @btn-padding-large: 12px 1rem; 74 | @btn-font-size-large: 1.8rem; 75 | //** small 76 | @btn-padding-small: 6px 1rem; 77 | @btn-font-size-small: 1.4rem; 78 | //** mini 79 | @btn-padding-mini: 4px 1rem; 80 | @btn-font-size-mini: 1.2rem; 81 | @btn-disabled-opacity: .7; 82 | 83 | 84 | // 表单 Form 85 | // -------------------------------------------- 86 | @form-label-margin: 0 0 5px 0; 87 | @form-radio-checkbox-margin:4px .7rem 0 0; 88 | //** input 89 | @form-input-height: 36px; 90 | @form-input-margin: 0 0 5px 0; 91 | @form-input-padding: 4px 6px; 92 | @form-input-border-color: #ccc; 93 | @form-input-border-radius: @border-radius-base; 94 | @form-input-placeholder: #999; 95 | @form-item-margin: 0 0 1.5rem 0; 96 | 97 | 98 | // 栅格系统 Grid system 99 | // -------------------------------------------- 100 | @grid-columns: 12; 101 | @gutter: 1.5rem; // 15px 102 | @grid-gutter-width: 3rem; // 30px 103 | 104 | 105 | // =========================================================================== 106 | // Components 107 | // =========================================================================== 108 | 109 | 110 | // 徽章 Badge 111 | // -------------------------------------------- 112 | @badge-border-radius: 10px; 113 | @badge-padding: 3px 7px; 114 | @badge-black: #444444; 115 | @badge-blue: #009bd8; 116 | @badge-green: #6eb82c; 117 | @badge-grey: #a9a7a7; 118 | @badge-red: #e34541; 119 | @badge-orange: #faa800; 120 | 121 | 122 | // 栏条 Bar 123 | // -------------------------------------------- 124 | @bar-normal-height: 49px; 125 | @bar-toolbar-padding: 0 4px; 126 | @bar-toolbar-index: 100; 127 | @bar-nav-extend-margin: 0 .5rem; 128 | @bar-title-font-size: @head-font-size; 129 | @bar-pagination-font-size: @content-font-size; 130 | @bar-tab-font-size: @content-font-size; 131 | @bar-search-padding: 0 8px; 132 | @bar-search-label-bg: @white; 133 | @bar-search-height: 32px; 134 | @bar-search-icon: ~"\f002"; 135 | @bar-search-btn-space: 4px; 136 | 137 | 138 | // 列表 List 139 | // -------------------------------------------- 140 | @list-font-size: @content-font-size; 141 | //@list-icon-width: 60px; 142 | //@list-icon-height: 60px; 143 | @list-padding: 12px; // 不能使用 top right bottom left 格式 144 | @list-line-height: 1.4; 145 | @list-badge-pos-top: -5px; 146 | @list-badge-pos-left: -5px; 147 | @list-overlay-zindex: 11; 148 | @list-inside-space: .5rem; 149 | @list-multi-border-color: #e5e5e5; 150 | @list-tile-padding: 10px 5px; 151 | 152 | 153 | // 画廊 Gallery 154 | // -------------------------------------------- 155 | @gallery-space: 3px; 156 | @gallery-title-font-size: 1.4rem; 157 | @gallery-title-font-desc: 1.4rem; 158 | @gallery-overlay-title-bg: rgba(0,0,0,.6); 159 | @gallery-overlay-title-color:@white; 160 | @gallery-item-min-height: 100px; 161 | 162 | 163 | // 字体图标 Icon 164 | // -------------------------------------------- 165 | @icon-font-family: "FontAwesome"; 166 | @icon-font-path: "../fonts/"; 167 | @icon-font-name: "fontawesome-webfont"; 168 | @icon-x2-size: 2rem; 169 | @icon-x3-size: 2.7rem; 170 | @icon-x4-size: 4rem; 171 | @icon-x5-size: 5rem; 172 | 173 | 174 | // ============================================================================= 175 | // Plugins 176 | // ============================================================================= 177 | 178 | 179 | // Dropdown 180 | // -------------------------------------------- 181 | @dropdown-content-bg: @white; 182 | @dropdown-content-radius: @border-radius-base; 183 | @dropdown-border-color: #e5e5e5; 184 | @dropdown-hover: #efefef; 185 | @dropdown-index: 100; 186 | @dropdown-content-padding: 7px 10px; 187 | @dropdown-min-width: 100px; 188 | 189 | 190 | // Loadmore 191 | // -------------------------------------------- 192 | @loadmore-lin-height: @default-line-height; 193 | @loadmore-padding: 8px; 194 | 195 | 196 | // Panel 197 | // -------------------------------------------- 198 | @panel-bg: #333; 199 | 200 | 201 | // Modal 202 | // -------------------------------------------- 203 | @modal-bg: @white; 204 | @modal-radius: @border-radius-base; // 不能使用 top right bottom left 格式 205 | @modal-border-color: #d7d7d7; 206 | @modal-btn-padding: 8px; -------------------------------------------------------------------------------- /example/loadmore.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Squirrel Loadmore 11 | 12 | 13 |
    14 | 26 |
    27 |

    Loadmore

    28 | 135 |
    136 |
    137 | Top 138 | 139 | 140 | 141 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /example/animate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Squirrel Animate 11 | 12 | 13 |
    14 | 26 |
    27 | 28 |
    29 |
    Animate Demo
    30 |
    31 |
    32 |

    引起注意(Attention Seekers)

    33 | bounce 34 | flash 35 | pulse 36 | rubberBand 37 | shake 38 | swing 39 | tada 40 | wobble 41 |

    弹入(Bouncing Entrances)

    42 | bounceIn 43 | bounceInDown 44 | bounceInLeft 45 | bounceInRight 46 | bounceInUp 47 |

    弹出(Bouncing Exits)

    48 | bounceOut 49 | bounceOutDown 50 | bounceOutLeft 51 | bounceOutRight 52 | bounceOutUp 53 |

    淡入(Fading Entrances)

    54 | fadeIn 55 | fadeInDown 56 | fadeInDownBig 57 | fadeInLeft 58 | fadeInLeftBig 59 | fadeInRight 60 | fadeInRightBig 61 | fadeInUp 62 | fadeInUpBig 63 |

    淡出(Fading Exits)

    64 | fadeOut 65 | fadeOutDown 66 | fadeOutDownBig 67 | fadeOutLeft 68 | fadeOutLeftBig 69 | fadeOutRight 70 | fadeOutRightBig 71 | fadeOutUp 72 | fadeOutUpBig 73 |

    Flippers

    74 | flip 75 | flipInX 76 | flipInY 77 | flipOutX 78 | flipOutY 79 |

    Lightspeed

    80 | lightSpeedIn 81 | lightSpeedOut 82 |

    Rotating Entrances

    83 | rotateIn 84 | rotateInDownLeft 85 | rotateInDownRight 86 | rotateInUpLeft 87 | rotateInUpRight 88 |

    Rotating Exits

    89 | rotateOut 90 | rotateOutDownLeft 91 | rotateOutDownRight 92 | rotateOutUpLeft 93 | rotateOutUpRight 94 |

    Specials

    95 | hinge 96 | rollIn 97 | rollOut 98 |

    Zoom Entrances

    99 | zoomIn 100 | zoomInDown 101 | zoomInLeft 102 | zoomInRight 103 | zoomInUp 104 |

    Zoom Exits

    105 | zoomOut 106 | zoomOutDown 107 | zoomOutLeft 108 | zoomOutRight 109 | zoomOutUp 110 |
    111 |
    112 |
    113 | Top 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /src/scripts/sticky.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file SQ.Sticky 悬停插件 3 | * @version 1.6.0 4 | */ 5 | 6 | /** 7 | * @changelog 8 | * 1.6.0 * 插件更名为 SQ.Sticky 9 | * 1.5.1 * 修复 $placeholderDom 高度计算问题 10 | * 1.5.0 * 重写插件,调用方式改为 $. 链式调用。 11 | * 1.0.0 + 新增 refresh 方法,可以刷新 Fixed 列表; 12 | * * 更改 ARRY_FIXED_POSITION 默认值,修正 fixed 元素高度时会占据全屏的 bug; 13 | * * 修正 triggerPosTop 没有将 scrollY 的值计算在内的 bug。 14 | * 0.9.0 * 完成主要功能 15 | * 0.0.1 + 新建。 16 | */ 17 | /*global $, SQ, console, jQuery */ 18 | (function ($) { 19 | 'use strict'; 20 | /** 21 | * @name Sticky 22 | * @classdesc 元素固定定位 23 | * @constructor 24 | * @param {object} config 插件配置(下面的参数为配置项,配置会写入属性) 25 | * @param {string} config.ANIMATE 动画类,默认值:undefined 26 | * @param {array} config.ARRY_FIXED_POSITION 固定位置设置,遵循 [上,右,下,左] 规则,默认为:[0, 0, 'auto', 0] 27 | * @param {number} config.NUM_TRIGGER_POSITION 设置 sticky 激活位置,当有该值时以该值为准,没有则以元素当前位置为准 28 | * @param {number} config.NUM_ZINDEX z-index 值设置,默认为 101 29 | * @param {boolen} config.PLACEHOLD 是否设置占位 DOM,默认为 false 30 | * @param {function} config.fixedIn 设置固定布局时回调函数 31 | * @param {function} config.fixedOut 取消固定布局时回调函数 32 | * @example $('.J_fixedHeader').sticky({ 33 | PLACEHOLD: true 34 | }); 35 | */ 36 | 37 | var scope = 'sq-sticky'; // data-* 后缀 38 | var defaults = { 39 | ARRY_FIXED_POSITION: [0, 0, 'auto', 0], 40 | NUM_ZINDEX: 101, // .sq-header 的 z-index 值为 100 41 | PLACEHOLD: false 42 | }; 43 | 44 | function Sticky ( element, options ) { 45 | this.element = element; 46 | this.settings = $.extend( {}, defaults, options ); 47 | this._defaults = defaults; 48 | this.init(); 49 | } 50 | 51 | Sticky.prototype = { 52 | construtor: 'Sticky', 53 | scrollTimer: 0, // 滑动计时器 54 | scrollDelay: 150, // 滑动阀值 55 | init: function () { 56 | var me = this; 57 | var initializedIndex = $('[data-' + scope + ']').length; 58 | me.$element = $(me.element); 59 | me.fixedInFun = me.settings.fixedIn; 60 | me.fixedOutFun = me.settings.fixedOut; 61 | 62 | me.$element.each(function (index) { 63 | var fixedItem = { 64 | id: scope + (index + initializedIndex), // 用于定位 sticky 元素 65 | self: this, 66 | $self: $(this), 67 | sticky: false // 标记是否处在 sticky 状态,用于之后的判断 68 | }; 69 | 70 | // 确定 sticky 激活位置,当有 NUM_TRIGGER_POSITION 值时以该值为准,没有则以元素当前位置为准 71 | if (me.settings.NUM_TRIGGER_POSITION && SQ.isNumber(me.settings.NUM_TRIGGER_POSITION)) { 72 | fixedItem.triggerPosTop = me.settings.NUM_TRIGGER_POSITION; 73 | } else { 74 | // 设置占位 DOM 75 | if (me.settings.PLACEHOLD) { 76 | me._setPlaceholder(fixedItem); 77 | } 78 | // 获取元素位置 top 值 79 | if (fixedItem.self.getBoundingClientRect()) { 80 | fixedItem.triggerPosTop = fixedItem.self.getBoundingClientRect().top + window.scrollY; 81 | } else { 82 | console.warn('Not Support getBoundingClientRect'); 83 | } 84 | // 当元素处于页面顶端则立即设置为 sticky 布局 85 | // UC 浏览器在实际渲染时会有问题,不建议用 sticky.js 来实现顶部导航的固定布局(直接使用 CSS) 86 | if (fixedItem.self.triggerPosTop === 0) { 87 | me._setFixed(fixedItem); 88 | } 89 | } 90 | // 触发绑定 91 | me._trigger(fixedItem); 92 | }); 93 | }, 94 | /** 95 | * 设置 sticky 元素占位 DOM 96 | * @param fixedItem 97 | * @private 98 | */ 99 | _setPlaceholder: function (fixedItem) { 100 | var $dom = fixedItem.$self; 101 | var height = $dom.height() + parseInt($dom.css('padding-top'), 10) + parseInt($dom.css('padding-bottom'), 10) + parseInt($dom.css('margin-top'), 10) + parseInt($dom.css('margin-bottom'), 10); 102 | var $placeholderDom = $('
    ').css({ 103 | display: 'none', 104 | width: $dom.width(), 105 | height: height, 106 | background: $dom.css('background') 107 | }); 108 | $placeholderDom.insertAfter(fixedItem.$self); 109 | }, 110 | /** 111 | * 设置触发事件及触发条件 112 | * @param fixedItem 113 | * @private 114 | */ 115 | _trigger: function (fixedItem) { 116 | var me = this; 117 | window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame; 118 | // 高级浏览器使用 requestAnimationFrame 119 | function advancedWatchEvent() { 120 | var scrollTop = window.scrollY; 121 | if (scrollTop >= fixedItem.triggerPosTop && !fixedItem.sticky) { 122 | me._setFixed(fixedItem); 123 | } else if (scrollTop < fixedItem.triggerPosTop && fixedItem.sticky) { 124 | me._removeFixed(fixedItem); 125 | } 126 | window.requestAnimationFrame(advancedWatchEvent); 127 | } 128 | // 不支持 requestAnimationFrame 的浏览器使用常用事件 129 | function normalWatchEvent() { 130 | var mobile = 'android-ios'; 131 | // 触发函数 132 | function fire() { 133 | var scrollTop = window.scrollY; 134 | if (scrollTop >= fixedItem.triggerPosTop && !fixedItem.$self.hasClass('sq-sticky')) { 135 | me._setFixed(fixedItem); 136 | } else if (scrollTop < fixedItem.triggerPosTop && fixedItem.$self.hasClass('sq-sticky')) { 137 | me._removeFixed(fixedItem); 138 | } 139 | } 140 | // 触摸设备使用 touchstart 事件 141 | if (mobile.indexOf(SQ.ua.os.name) !== -1) { 142 | $(window).on('touchstart', function () { 143 | // 在触摸滑动时浏览器会锁死进程,滑动停止后才会触发 touchstart 事件,而此时 scrollTop 值 144 | // 为触摸时的数值,所以添加 setTimeout 来计算获取滑动停止后的数值。 145 | setTimeout(function () { 146 | fire(); 147 | }, 150); 148 | }); 149 | } else { 150 | $(window).on('scroll', function () { 151 | // 添加 scroll 事件相应伐值,优化其性能 152 | if (!me.scrollTimer) { 153 | me.scrollTimer = setTimeout(function () { 154 | fire(); 155 | me.scrollTimer = 0; 156 | }, me.scrollDelay); 157 | } 158 | }); 159 | } 160 | } 161 | 162 | if (window.requestAnimationFrame) { 163 | window.requestAnimationFrame(advancedWatchEvent); 164 | } else { 165 | normalWatchEvent(); 166 | } 167 | }, 168 | _setFixed: function (fixedItem) { 169 | var me = this; 170 | var posCss = me.settings.ARRY_FIXED_POSITION; 171 | var $placeholderDom = $('#' + fixedItem.id); 172 | 173 | fixedItem.$self.css({ 174 | 'position': 'fixed', 175 | 'top': posCss[0], 176 | 'right': posCss[1], 177 | 'bottom': posCss[2], 178 | 'left': posCss[3], 179 | 'z-index': me.settings.NUM_ZINDEX 180 | }); 181 | fixedItem.sticky = true; 182 | 183 | if (me.settings.PLACEHOLD && $placeholderDom.length) { 184 | $placeholderDom.show(); 185 | } 186 | 187 | if (me.settings.ANIMATE) { 188 | var animateClassName = me.settings.ANIMATE.indexOf('.') === 0 ? me.settings.ANIMATE.slice(1) : me.settings.ANIMATE; 189 | fixedItem.$self.addClass('animated ' + animateClassName); 190 | } 191 | 192 | if (me.fixedInFun) { 193 | me.fixedInFun(); 194 | } 195 | }, 196 | _removeFixed: function (fixedItem) { 197 | var me = this; 198 | var $placeholderDom = $('#' + fixedItem.id); 199 | 200 | fixedItem.$self.attr('style', ''); 201 | fixedItem.sticky = false; 202 | 203 | if (me.settings.PLACEHOLD && $placeholderDom.length) { 204 | $placeholderDom.hide(); 205 | } 206 | 207 | if (me.fixedOutFun) { 208 | me.fixedOutFun(); 209 | } 210 | } 211 | }; 212 | 213 | $.fn.sticky = function ( options ) { 214 | var isZepto = typeof Zepto !== 'undefined' ? true : false; 215 | var isJQuery = typeof jQuery !== 'undefined' ? true : false; 216 | var plugin; 217 | 218 | options = options || {}; 219 | options.selector = this.selector; 220 | 221 | this.each(function() { 222 | if (isJQuery) { 223 | if ( !$.data( this, scope ) ) { 224 | $.data( this, scope, new Sticky( this, options ) ); 225 | } 226 | } else if (isZepto) { 227 | if (!$(this).data(scope)) { 228 | plugin = new Sticky( this, options ); 229 | $(this).data(scope, 'initialized'); 230 | } 231 | } 232 | }); 233 | // chain jQuery functions 234 | return this; 235 | }; 236 | 237 | })($); -------------------------------------------------------------------------------- /src/styles/normalize.less: -------------------------------------------------------------------------------- 1 | // 2 | // Normalize 3 | // @desc: 标准化样式 4 | // --------------------------------------------------------------------------- 5 | 6 | // normalize.css 7 | // 以下为 normalize.css 拷贝 8 | // 更新时请使用 bower 9 | // -------------------------------------------- 10 | 11 | /* normalize.css v3.0.2 | MIT License | git.io/normalize */ 12 | 13 | /** 14 | * 1. Set default font family to sans-serif. 15 | * 2. Prevent iOS text size adjust after orientation change, without disabling 16 | * user zoom. 17 | */ 18 | 19 | html { 20 | font-family: sans-serif; /* 1 */ 21 | -ms-text-size-adjust: 100%; /* 2 */ 22 | -webkit-text-size-adjust: 100%; /* 2 */ 23 | } 24 | 25 | /** 26 | * Remove default margin. 27 | */ 28 | 29 | body { 30 | margin: 0; 31 | } 32 | 33 | /* HTML5 display definitions 34 | ========================================================================== */ 35 | 36 | /** 37 | * Correct `block` display not defined for any HTML5 element in IE 8/9. 38 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 39 | * and Firefox. 40 | * Correct `block` display not defined for `main` in IE 11. 41 | */ 42 | 43 | article, 44 | aside, 45 | details, 46 | figcaption, 47 | figure, 48 | footer, 49 | header, 50 | hgroup, 51 | main, 52 | menu, 53 | nav, 54 | section, 55 | summary { 56 | display: block; 57 | } 58 | 59 | /** 60 | * 1. Correct `inline-block` display not defined in IE 8/9. 61 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 62 | */ 63 | 64 | audio, 65 | canvas, 66 | progress, 67 | video { 68 | display: inline-block; /* 1 */ 69 | vertical-align: baseline; /* 2 */ 70 | } 71 | 72 | /** 73 | * Prevent modern browsers from displaying `audio` without controls. 74 | * Remove excess height in iOS 5 devices. 75 | */ 76 | 77 | audio:not([controls]) { 78 | display: none; 79 | height: 0; 80 | } 81 | 82 | /** 83 | * Address `[hidden]` styling not present in IE 8/9/10. 84 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. 85 | */ 86 | 87 | [hidden], 88 | template { 89 | display: none; 90 | } 91 | 92 | /* Links 93 | ========================================================================== */ 94 | 95 | /** 96 | * Remove the gray background color from active links in IE 10. 97 | */ 98 | 99 | a { 100 | background-color: transparent; 101 | } 102 | 103 | /** 104 | * Improve readability when focused and also mouse hovered in all browsers. 105 | */ 106 | 107 | a:active, 108 | a:hover { 109 | outline: 0; 110 | } 111 | 112 | /* Text-level semantics 113 | ========================================================================== */ 114 | 115 | /** 116 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome. 117 | */ 118 | 119 | abbr[title] { 120 | border-bottom: 1px dotted; 121 | } 122 | 123 | /** 124 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 125 | */ 126 | 127 | b, 128 | strong { 129 | font-weight: bold; 130 | } 131 | 132 | /** 133 | * Address styling not present in Safari and Chrome. 134 | */ 135 | 136 | dfn { 137 | font-style: italic; 138 | } 139 | 140 | /** 141 | * Address variable `h1` font-size and margin within `section` and `article` 142 | * contexts in Firefox 4+, Safari, and Chrome. 143 | */ 144 | 145 | h1 { 146 | font-size: 2em; 147 | margin: 0.67em 0; 148 | } 149 | 150 | /** 151 | * Address styling not present in IE 8/9. 152 | */ 153 | 154 | mark { 155 | background: #ff0; 156 | color: #000; 157 | } 158 | 159 | /** 160 | * Address inconsistent and variable font size in all browsers. 161 | */ 162 | 163 | small { 164 | font-size: 80%; 165 | } 166 | 167 | /** 168 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 169 | */ 170 | 171 | sub, 172 | sup { 173 | font-size: 75%; 174 | line-height: 0; 175 | position: relative; 176 | vertical-align: baseline; 177 | } 178 | 179 | sup { 180 | top: -0.5em; 181 | } 182 | 183 | sub { 184 | bottom: -0.25em; 185 | } 186 | 187 | /* Embedded content 188 | ========================================================================== */ 189 | 190 | /** 191 | * Remove border when inside `a` element in IE 8/9/10. 192 | */ 193 | 194 | img { 195 | border: 0; 196 | } 197 | 198 | /** 199 | * Correct overflow not hidden in IE 9/10/11. 200 | */ 201 | 202 | svg:not(:root) { 203 | overflow: hidden; 204 | } 205 | 206 | /* Grouping content 207 | ========================================================================== */ 208 | 209 | /** 210 | * Address margin not present in IE 8/9 and Safari. 211 | */ 212 | 213 | figure { 214 | margin: 1em 40px; 215 | } 216 | 217 | /** 218 | * Address differences between Firefox and other browsers. 219 | */ 220 | 221 | hr { 222 | -moz-box-sizing: content-box; 223 | box-sizing: content-box; 224 | height: 0; 225 | } 226 | 227 | /** 228 | * Contain overflow in all browsers. 229 | */ 230 | 231 | pre { 232 | overflow: auto; 233 | } 234 | 235 | /** 236 | * Address odd `em`-unit font size rendering in all browsers. 237 | */ 238 | 239 | code, 240 | kbd, 241 | pre, 242 | samp { 243 | font-family: monospace, monospace; 244 | font-size: 1em; 245 | } 246 | 247 | /* Forms 248 | ========================================================================== */ 249 | 250 | /** 251 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 252 | * styling of `select`, unless a `border` property is set. 253 | */ 254 | 255 | /** 256 | * 1. Correct color not being inherited. 257 | * Known issue: affects color of disabled elements. 258 | * 2. Correct font properties not being inherited. 259 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 260 | */ 261 | 262 | button, 263 | input, 264 | optgroup, 265 | select, 266 | textarea { 267 | color: inherit; /* 1 */ 268 | font: inherit; /* 2 */ 269 | margin: 0; /* 3 */ 270 | } 271 | 272 | /** 273 | * Address `overflow` set to `hidden` in IE 8/9/10/11. 274 | */ 275 | 276 | button { 277 | overflow: visible; 278 | } 279 | 280 | /** 281 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 282 | * All other form control elements do not inherit `text-transform` values. 283 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 284 | * Correct `select` style inheritance in Firefox. 285 | */ 286 | 287 | button, 288 | select { 289 | text-transform: none; 290 | } 291 | 292 | /** 293 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 294 | * and `video` controls. 295 | * 2. Correct inability to style clickable `input` types in iOS. 296 | * 3. Improve usability and consistency of cursor style between image-type 297 | * `input` and others. 298 | */ 299 | 300 | button, 301 | html input[type="button"], /* 1 */ 302 | input[type="reset"], 303 | input[type="submit"] { 304 | -webkit-appearance: button; /* 2 */ 305 | cursor: pointer; /* 3 */ 306 | } 307 | 308 | /** 309 | * Re-set default cursor for disabled elements. 310 | */ 311 | 312 | button[disabled], 313 | html input[disabled] { 314 | cursor: default; 315 | } 316 | 317 | /** 318 | * Remove inner padding and border in Firefox 4+. 319 | */ 320 | 321 | button::-moz-focus-inner, 322 | input::-moz-focus-inner { 323 | border: 0; 324 | padding: 0; 325 | } 326 | 327 | /** 328 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 329 | * the UA stylesheet. 330 | */ 331 | 332 | input { 333 | line-height: normal; 334 | } 335 | 336 | /** 337 | * It's recommended that you don't attempt to style these elements. 338 | * Firefox's implementation doesn't respect box-sizing, padding, or width. 339 | * 340 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 341 | * 2. Remove excess padding in IE 8/9/10. 342 | */ 343 | 344 | input[type="checkbox"], 345 | input[type="radio"] { 346 | box-sizing: border-box; /* 1 */ 347 | padding: 0; /* 2 */ 348 | } 349 | 350 | /** 351 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain 352 | * `font-size` values of the `input`, it causes the cursor style of the 353 | * decrement button to change from `default` to `text`. 354 | */ 355 | 356 | input[type="number"]::-webkit-inner-spin-button, 357 | input[type="number"]::-webkit-outer-spin-button { 358 | height: auto; 359 | } 360 | 361 | /** 362 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome. 363 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome 364 | * (include `-moz` to future-proof). 365 | */ 366 | 367 | input[type="search"] { 368 | -webkit-appearance: textfield; /* 1 */ 369 | -moz-box-sizing: content-box; 370 | -webkit-box-sizing: content-box; /* 2 */ 371 | box-sizing: content-box; 372 | } 373 | 374 | /** 375 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. 376 | * Safari (but not Chrome) clips the cancel button when the search input has 377 | * padding (and `textfield` appearance). 378 | */ 379 | 380 | input[type="search"]::-webkit-search-cancel-button, 381 | input[type="search"]::-webkit-search-decoration { 382 | -webkit-appearance: none; 383 | } 384 | 385 | /** 386 | * Define consistent border, margin, and padding. 387 | */ 388 | 389 | fieldset { 390 | border: 1px solid #c0c0c0; 391 | margin: 0 2px; 392 | padding: 0.35em 0.625em 0.75em; 393 | } 394 | 395 | /** 396 | * 1. Correct `color` not being inherited in IE 8/9/10/11. 397 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 398 | */ 399 | 400 | legend { 401 | border: 0; /* 1 */ 402 | padding: 0; /* 2 */ 403 | } 404 | 405 | /** 406 | * Remove default vertical scrollbar in IE 8/9/10/11. 407 | */ 408 | 409 | textarea { 410 | overflow: auto; 411 | } 412 | 413 | /** 414 | * Don't inherit the `font-weight` (applied by a rule above). 415 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 416 | */ 417 | 418 | optgroup { 419 | font-weight: bold; 420 | } 421 | 422 | /* Tables 423 | ========================================================================== */ 424 | 425 | /** 426 | * Remove most spacing between table cells. 427 | */ 428 | 429 | table { 430 | border-collapse: collapse; 431 | border-spacing: 0; 432 | } 433 | 434 | td, 435 | th { 436 | padding: 0; 437 | } 438 | -------------------------------------------------------------------------------- /doc_tpl/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Squirrel 前端开发框架 12 | 13 | 14 | 18 |
    19 |
    20 | 33 |
    34 | 48 |
    49 |
    50 |

    框架特性

    51 |

    Squirrel 框架包含许多实用特性,帮助你快速构建移动 Web 应用。

    52 |
    53 |
    54 |
      55 |
    • 56 |
      57 |
      58 |

      组件丰富

      59 |

      Squirrel 框架提供丰富的样式及交互组件,能帮助您轻松构建 Web 应用。

      60 |
      61 |
    • 62 |
    • 63 |
      64 |
      65 |

      多系统支持

      66 |

      默认支持 Android、iOS 操作系统,也可方便的扩展支持 WP8、Firefox OS 等系统。

      67 |
      68 |
    • 69 |
    • 70 |
      71 |
      72 |

      响应式设计

      73 |

      能适应各种屏幕尺寸、分辨率的设备,并针对触摸操作体验进行了优化。

      74 |
      75 |
    • 76 |
    • 77 |
      78 |
      79 |

      扩展简便

      80 |

      采用 LESS 技术、CMD 规范、jQuery 插件开发模式,无论扩展还是移植都方便。

      81 |
      82 |
    • 83 |
    • 84 |
      85 |
      86 |

      定制灵活

      87 |

      提供定制工具,可以根据实际需要灵活的定制框架。

      88 |
      89 |
    • 90 |
    • 91 |
      92 |
      93 |

      开源协议

      94 |

      Squirrel 框架遵循 MIT 协议,无论公司还是个人,都可以免费、自由使用。

      95 |
      96 |
    • 97 |
    98 |
    99 |
    100 |
    101 |
    102 |

    演示

    103 |

    借助 Squirrel,您可以非常方便的构建移动 Web 应用,下面是由 Squirrel 框架构建演示案例。

    104 |
    105 |
    106 |
    107 |
    108 |
    109 |
    110 |
    111 |
    112 |
    113 |
    114 |
    115 |
    116 | 117 |
    118 |
    119 |
    120 |
    121 |
    122 |
    123 | 二维码 124 |

    使用手机浏览体验

    125 |
    126 |
    127 |
    128 |
    129 |
    130 |

    合作伙伴

    131 |

    Squirrel 为以团队下提供了技术支持。

    132 |
      133 |
    • 134 |
    • 135 |
    136 |
    137 |
    138 |
    139 |
    140 |

    贡献者

    141 |

    感谢所有为 Squirrel 贡献力量的朋友们,因为你们她才会不断成长。这里也欢迎更多的朋友加入 Squirrel 开源项目!

    142 |
    143 | 144 | 165 | 166 |
    167 |
      168 |
    • 169 |

      插件

      170 |

      欢迎您为 Squirrel 贡献高实用性的功能插件,以方便其他使用者。开始之前请先阅读插件开发流程

      171 |
    • 172 |
    • 173 |

      主题

      174 |

      如果您制作了一款基于 Squirrel 的主题并且符合主题的开发规范,我们非常欢迎您将它提交至主题库

      175 |
    • 176 |
    • 177 |

      模式库

      178 |

      模式库(DPL)组件与业务结合紧密,具备高质量且完整的交互体验,如果您为您的项目开发了类似的组件,欢迎提交至设计模式库

      179 |
    • 180 |
    • 181 |

      用户体验

      182 |

      希望您能将任何体验相关的意见或建议,甚至新的功能需求提交至 github issues「体验」。

      183 |
    • 184 |
    • 185 |

      缺陷

      186 |

      如果使用中发现任何缺陷(BUG),请提交至 github issues「BUG」,同时也请报告您的使用版本、环境,我们会尽快确认、修复。

      187 |
    • 188 |
    189 |
    190 |
    191 | 207 |
    208 | 209 | 210 | -------------------------------------------------------------------------------- /src/scripts/suggest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file SQ.Suggest 联想词插件 3 | * @version 1.0.0 4 | */ 5 | 6 | /** 7 | * @changelog 8 | * 1.0.0 * 重写插件,调用方式改为 $. 链式调用。 9 | * 0.5.10 * 修复 jshint 问题 10 | * 0.5.9 * 修复在输入搜索后删除搜索词,再次输入相同字符,首字符无请求问题。issues#11 11 | * 0.5.8 * 修复 IE 下对 XHR 对象处理问题。 12 | * 0.5.7 * 修复多次发送请求时,老请求因为响应慢覆盖新请求问题。 13 | * 0.5.6 * 修改插件名称为 Suggest。 14 | * 0.5.5 * 完成搜索联想词基本功能。 15 | * 0.0.1 + 新建。 16 | */ 17 | 18 | /*global $, SQ, console, jQuery */ 19 | (function ($) { 20 | 'use strict'; 21 | /** 22 | * @name Suggest 23 | * @classdesc 搜索联想词插件 24 | * @constructor 25 | * @param {object} config 插件配置(下面的参数为配置项,配置会写入属性) 26 | * @param {string} config.API 联想词接口 27 | * @param {string} config.CSS_CLEAR_BTN 设置清空按钮样式名称,默认为 .sq-suggest-clear-btn 28 | * @param {string} config.CSS_SUGGEST_PANEL 设置联想词展示面板样式名称,默认为 .sq-suggest-result 29 | * @param {number} config.NUM_TIMER_DELAY 监测输入框间隔时长,默认为 300ms 30 | * @param {number} config.NUM_XHR_TIMEER XHR 超时时长,默认为 5000ms 31 | * @param {number} config.NUM_SUCCESS_CODE XHR 成功状态码,默认为 200 32 | * @param {function} config.beforeStart 检测输入框前的回调函数 33 | * @param {function} config.start 开始检测输入框时回调函数 34 | * @param {function} config.show(data) 显示联想词面板时回调函数,data 为 XHR 返回数据 35 | * @param {function} config.clear 清除时回调函数 36 | * @example $('.J_suggest').suggest({ 37 | API: 'data/suggest.json', 38 | CSS_CLEAR_BTN: '.clear', 39 | CSS_SUGGEST_RESULT: '.suggest-panel', 40 | show: function (data) { 41 | var me = this; 42 | console.log('suggestList: ' + data); 43 | } 44 | }); 45 | */ 46 | var scope = 'sq-suggest'; 47 | var defaults = { 48 | NUM_TIMER_DELAY : 300, 49 | NUM_XHR_TIMEER : 5000, 50 | NUM_SUCCESS_CODE : 200, 51 | suggestion : true 52 | }; 53 | 54 | function Suggest ( element, options ) { 55 | this.element = element; 56 | this.settings = $.extend( {}, defaults, options ); 57 | this._defaults = defaults; 58 | this.init(); 59 | } 60 | 61 | Suggest.prototype = { 62 | construtor: 'Suggest', 63 | lastKeyword: '', // 为 300ms(检测时长) 前的关键词 64 | lastSendKeyword : '', // 上一次符合搜索条件的关键词 65 | canSendRequest : true, // 是否可以进行下次联想请求 66 | init: function () { 67 | var me = this; 68 | var clearBtnClassName = ''; 69 | var suggestResultClassName = ''; 70 | 71 | if (me.settings.CSS_CLEAR_BTN) { 72 | clearBtnClassName = me.settings.CSS_CLEAR_BTN.indexOf('.') !== -1 ? me.settings.CSS_CLEAR_BTN.slice(1) : me.settings.CSS_CLEAR_BTN; 73 | } 74 | if (me.settings.CSS_SUGGEST_RESULT) { 75 | suggestResultClassName = me.settings.CSS_SUGGEST_RESULT.indexOf('.') !== -1 ? me.settings.CSS_SUGGEST_RESULT.slice(1) : me.settings.CSS_SUGGEST_RESULT; 76 | } 77 | 78 | me.$element = $(me.element); 79 | me.$input = me.$element.find('input[type=text]'); 80 | me.$clearBtn = $('
    ').addClass(clearBtnClassName); 81 | me.$suggestPanel = $('
    ').addClass(suggestResultClassName); 82 | 83 | me.$input.after(me.$clearBtn); 84 | me.$element.append(me.$suggestPanel); 85 | 86 | me.beforeStartFun = me.settings.beforeStart; 87 | me.startFun = me.settings.start; 88 | me.clearFun = me.settings.clear; 89 | me.showFun = me.settings.show; 90 | 91 | if (me._verify()) { 92 | me._bind(); 93 | } 94 | }, 95 | _verify : function () { 96 | return true; 97 | }, 98 | _bind : function (e) { 99 | var me = this; 100 | me.$input.on('focus', function () { 101 | me.start(); 102 | }); 103 | me.$input.on('blur', function () { 104 | me.stop(); 105 | }); 106 | me.$clearBtn.on('click', function () { 107 | me.clear(); 108 | }); 109 | if (me.beforeStartFun) { 110 | me.beforeStartFun(); 111 | } 112 | }, 113 | /** 过滤输入内容 */ 114 | _filter : function (originalKeyword) { 115 | return originalKeyword.replace(/\s+/g, '').replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, ''); 116 | }, 117 | /** 初始化提示层容器 */ 118 | _initSuggest : function () { 119 | var me = this; 120 | me.$suggestPanel.empty(); 121 | }, 122 | /** 请求数据 */ 123 | _requestData : function (keyword) { 124 | var me = this; 125 | var api = me.settings.API; 126 | 127 | //console.log('request -> ' + 'keyword: ' + keyword, 'lastSendKeyword: ' + me.lastSendKeyword); 128 | if (me.xhr) { 129 | me.xhr.abort(); 130 | } 131 | me.xhr = $.ajax({ 132 | type : 'POST', 133 | url : api, 134 | dataType : 'json', 135 | data : {'keyword': keyword}, 136 | timeout : me.settings.NUM_XHR_TIMEER, 137 | success : function (data) { 138 | me.showSuggest(data); 139 | me.lastSendKeyword = keyword; 140 | }, 141 | error : function () { 142 | me.canSendRequest = false; 143 | setTimeout(function () { 144 | me.canSendRequest = true; 145 | }, 500); 146 | } 147 | }); 148 | }, 149 | _compare : function (keyword) { 150 | var me = this; 151 | var cLen = keyword.length; 152 | var lsLen = me.lastSendKeyword.length; 153 | //console.log('keyword: ' + keyword, 'lastSendKeyword: ' + me.lastSendKeyword); 154 | 155 | if (me.lastKeyword === keyword) { 156 | //console.log('same ' + 'me.lastKeyword = ' + me.lastKeyword + ' | ' + 'keyword = ' + keyword + ' | ' + 'me.lastSendKeyword =' + me.lastSendKeyword); 157 | return false; 158 | } 159 | 160 | if (lsLen > 0 && cLen < lsLen) { 161 | me.canSendRequest = true; 162 | } 163 | 164 | if (!me.canSendRequest) { 165 | // canSendRequest 为能否发送请求的判断条件 166 | // 有几种情况会改变 canSendRequest 的值: 167 | // true 情况 168 | // 1、当前输入关键词少于上次发送请求关键词时,canSendRequest 为 true 169 | // 2、请求服务器成功返回并有联想结果时,canSendRequest 为 true 170 | // 3、调用 clear() 函数时,canSendRequest 为 true 171 | // 4、请求服务器失败,500ms 后 canSendRequest 为 true 172 | // false 情况 173 | // 1、请求服务器成功,但返回的 code 与 NUM_SUCCESS_CODE 不一致,canSendRequest 为 false 174 | // 2、请求服务器失败,canSendRequest 为 false 175 | //console.log('!canSendRequest'); 176 | return false; 177 | } 178 | if (me.lastSendKeyword === keyword) { 179 | //console.log('关键词相同') 180 | return false; 181 | } 182 | return true; 183 | }, 184 | /** 启动计时器,开始监听用户输入 */ 185 | start : function () { 186 | var me = this; 187 | me.inputListener = setInterval(function () { 188 | var originalKeyword = me.$input.val(); 189 | var keyword = me._filter(originalKeyword); 190 | 191 | if (keyword.length > 0) { 192 | if (me.$clearBtn.css('display') === 'none') { 193 | me.$clearBtn.show(); 194 | } 195 | if (me._compare(keyword)) { 196 | me._requestData(keyword); 197 | if (me.startFun) { 198 | me.startFun(); 199 | } 200 | } 201 | me.lastKeyword = keyword; 202 | } else { 203 | me.lastKeyword = undefined; 204 | me.clear(); 205 | } 206 | }, me.settings.NUM_TIMER_DELAY); 207 | }, 208 | /** 停止计时器 */ 209 | stop : function () { 210 | var me = this; 211 | clearInterval(me.inputListener); 212 | }, 213 | /** 显示提示层 */ 214 | showSuggest : function (data) { 215 | var me = this; 216 | var ds = typeof data === 'object' ? data : JSON.parse(data); 217 | if (ds.code !== me.settings.NUM_SUCCESS_CODE) { 218 | me.canSendRequest = false; 219 | return; 220 | } 221 | me.canSendRequest = true; 222 | me._initSuggest(); 223 | if (me.showFun) { 224 | me.showFun(ds); 225 | } 226 | }, 227 | /** 隐藏提示层 */ 228 | hideSuggest : function () { 229 | var me = this; 230 | me.$suggestPanel.hide(); 231 | }, 232 | /** 清除输入内容 */ 233 | clear : function () { 234 | var me = this; 235 | me.$input.val(''); 236 | me.hideSuggest(); 237 | me.$clearBtn.hide(); 238 | me.canSendRequest = true; 239 | me.lastSendKeyword = ''; 240 | if (me.clearFun) { 241 | me.clearFun(); 242 | } 243 | } 244 | }; 245 | 246 | $.fn.suggest = function ( options ) { 247 | var isZepto = typeof Zepto !== 'undefined' ? true : false; 248 | var isJQuery = typeof jQuery !== 'undefined' ? true : false; 249 | var plugin; 250 | 251 | options = options || {}; 252 | options.selector = this.selector; 253 | 254 | this.each(function() { 255 | if (isJQuery) { 256 | if ( !$.data( this, scope ) ) { 257 | $.data( this, scope, new Suggest( this, options ) ); 258 | } 259 | } else if (isZepto) { 260 | if (!$(this).data(scope)) { 261 | plugin = new Suggest( this, options ); 262 | $(this).data(scope, 'initialized'); 263 | } 264 | } 265 | }); 266 | // chain jQuery functions 267 | return this; 268 | }; 269 | 270 | })($); --------------------------------------------------------------------------------