├── .babelrc ├── .eslintrc.json ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── question-or-discuss.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── dist └── index.js ├── docs ├── css │ ├── app.12df65ee.css │ ├── chat-room.9f37805a.css │ ├── dynamic-size.9013d8ce.css │ ├── fixed-size.840ec49f.css │ ├── horizontal.50e26ffa.css │ ├── infinite-loading.4b238c18.css │ ├── keep-state.2736386a.css │ └── page-mode.bb1117ff.css ├── favicon.png ├── highlight │ ├── pack.js │ └── theme.css ├── index.html ├── index.js ├── js │ ├── app.a8ad5cb0.js │ ├── chat-room.c08970e6.js │ ├── chat-room~dynamic-size~fixed-size~horizontal~infinite-loading~keep-state~page-mode.c6537394.js │ ├── chunk-vendors.c7cc4591.js │ ├── dynamic-size.3003e8a2.js │ ├── fixed-size.1b9763e9.js │ ├── horizontal.ad2c5dae.js │ ├── infinite-loading.ce8d4372.js │ ├── keep-state.08df8235.js │ └── page-mode.7fc3339d.js └── milligram.css ├── example ├── babel.config.js ├── package-lock.json ├── package.json ├── public │ ├── favicon.png │ ├── highlight │ │ ├── pack.js │ │ └── theme.css │ ├── index.html │ └── milligram.css ├── readme.md ├── src │ ├── App.vue │ ├── common │ │ ├── const.js │ │ ├── gen-unique-id.js │ │ ├── get-code-url.js │ │ ├── mock.js │ │ ├── sentences.js │ │ ├── ua.js │ │ └── user.js │ ├── components │ │ ├── CodeHighLight.vue │ │ ├── Corner.vue │ │ ├── Introduction.vue │ │ └── Tab.vue │ ├── main.js │ ├── mixins │ │ └── index.js │ ├── router │ │ └── index.js │ └── views │ │ ├── chat-room │ │ ├── Editor.vue │ │ ├── Item.vue │ │ ├── Main.vue │ │ ├── Toolbar.vue │ │ └── util.js │ │ ├── dynamic-size │ │ ├── Code.vue │ │ ├── Item.vue │ │ └── Main.vue │ │ ├── fixed-size │ │ ├── Code.vue │ │ ├── Item.vue │ │ └── Main.vue │ │ ├── home │ │ └── Main.vue │ │ ├── horizontal │ │ ├── Code.vue │ │ ├── Item.vue │ │ └── Main.vue │ │ ├── infinite-loading │ │ ├── Code.vue │ │ ├── Item.vue │ │ └── Main.vue │ │ ├── keep-state │ │ ├── Code.vue │ │ ├── Item.vue │ │ └── Main.vue │ │ └── page-mode │ │ ├── Item.vue │ │ └── Main.vue └── vue.config.js ├── jest.config.js ├── package-lock.json ├── package.json ├── scripts ├── rollup.banner.js ├── rollup.development.js └── rollup.production.js ├── src ├── index.js ├── item.js ├── props.js └── virtual.js └── test ├── base.test.js ├── element.test.js ├── extra-props.test.js ├── extra-props2.test.js ├── horizontal.test.js ├── item.vue ├── offset.test.js ├── scroll.test.js ├── slot.test.js ├── start.test.js └── util.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["standard"], 3 | "env": { 4 | "browser": true, 5 | "amd": true 6 | }, 7 | "globals": { 8 | "it": "readonly", 9 | "expect": "readonly", 10 | "describe": "readonly" 11 | }, 12 | "parserOptions": { 13 | "ecmaVersion": 2018, 14 | "sourceType": "module" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-detectable=true 2 | *.css linguist-detectable=false 3 | *.vue linguist-detectable=false 4 | *.html linguist-detectable=false 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug report to help improve. 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Describe 11 | A clear and concise description of what the bug is. 12 | 13 | ## To Reproduce 14 | Steps to reproduce the behavior: 15 | 1. Click on '....' 16 | 2. Scroll down to '....' 17 | 3. See error 18 | 19 | ## Reproduce demo 20 | Providing a reproduce demo of this bug, that will help me solve the problem more quickly. You can fork by this online demo: https://codesandbox.io/s/live-demo-virtual-list-e1ww1 21 | 22 | ## Other 23 | - Version [e.g. 2.0] 24 | - Browser [e.g. chrome, safari] 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this component. 4 | title: '' 5 | labels: suggestion 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Describe the feature request 11 | 12 | A clear and concise description of what you want to happen. Or a clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question-or-discuss.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question or discuss 3 | about: Ask your questions or topics you want to discuss. 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **What kind of this PR?** (check at least one) 2 | 3 | - [ ] Bugfix 4 | - [ ] Feature 5 | - [ ] Code style update 6 | - [ ] Build-related changes 7 | - [ ] Other, please describe: 8 | 9 | **Other information:** 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | dist/*.map 4 | docs/js/*.map 5 | dev 6 | issues 7 | node_modules 8 | coverage 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 8 4 | script: 5 | - npm run lint 6 | branches: 7 | only: 8 | - master 9 | after_success: 10 | # - bash <(curl -s https://codecov.io/bash) 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 tangbc 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 all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 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 THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /docs/css/app.12df65ee.css: -------------------------------------------------------------------------------- 1 | body{font-size:18px}h1,h2,h3,h4,h5,h6{letter-spacing:0}@media (max-width:640px){#app{padding:3px;width:100%}}#nav{position:fixed;top:0;left:0;right:0;background-color:#9b4cca;color:#fff;height:40px;display:flex;align-items:center;padding-left:1em;white-space:nowrap;overflow-x:auto;z-index:2}@media (max-width:640px){#nav{padding:0;position:relative;background-color:unset;color:unset;height:unset;padding-left:unset;align-items:unset}}#nav a{display:inline-block;color:#fff}@media (max-width:640px){#nav a{margin-bottom:0;margin-right:1em;color:#9b4dca}}#nav .line{margin:0 1em;font-size:14px;color:pink}@media (max-width:640px){#nav .line{display:none}}#nav .router-link-exact-active,#nav .router-link-exact-active:hover{color:inherit;cursor:default;text-decoration:underline}@media (max-width:640px){#nav .router-link-exact-active,#nav .router-link-exact-active:hover{border-bottom:1px solid;border-color:#606c76;text-decoration:none}}.example{margin:0 auto;padding:0 2em;width:776px;padding-top:3em}@media (max-width:640px){.example{margin:unset;padding:unset;width:unset;padding-top:unset}}.example-content{margin-top:1em;background-color:#fff;position:relative;z-index:1}.scroll-touch{-webkit-overflow-scrolling:touch}.scroll-touch::-webkit-scrollbar{width:10px}.scroll-touch::-webkit-scrollbar-track{background:#f4f4f4}.scroll-touch::-webkit-scrollbar-thumb{border-radius:0;background:rgba(0,0,0,.12)}.scroll-touch::-webkit-scrollbar-thumb:hover{background:#b2b2b2}code{background-color:pink!important}.name[data-v-1c868cc0]{margin-top:1em}.desc[data-v-1c868cc0]{padding-bottom:3em}.icons[data-v-1c868cc0]{padding-bottom:1em}.icons .btn[data-v-1c868cc0]{margin-right:1em}.head[data-v-1c868cc0]{margin-bottom:3em}.head img[data-v-1c868cc0]{margin-right:.5em;width:auto;height:24px}.title[data-v-1c868cc0]{margin:1em 0}ul[data-v-1c868cc0]{padding-left:.5em}li[data-v-1c868cc0]{list-style-position:outside;margin-left:1em}.introduction[data-v-0c507b69]{font-size:16px;padding:.5em 1em;border-radius:3px;background-color:#f8f8ff}@media (max-width:640px){.introduction[data-v-0c507b69]{display:none}}pre[data-v-99fd125a]{border:none;padding:1em;font-size:14px;border-radius:3px;font-family:Consolas,Monaco,Andale Mono,Lucida Console,monospace}.github-corner[data-v-fc50af34]{z-index:3;text-transform:capitalize;padding-left:30px;width:90px;height:40px;line-height:40px;position:fixed;right:0;top:0;color:#fff;font-size:14px;font-weight:700;background-size:30px 30px;background-repeat:no-repeat;background-position:0;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg class='icon' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cpath d='M841.6 268.8c32-57.6-3.2-134.4-3.2-134.4-80 0-137.6 54.4-137.6 54.4-32-19.2-134.4-19.2-134.4-19.2s-102.4 0-134.4 19.2c0 0-57.6-54.4-137.6-54.4 0 0-35.2 76.8-3.2 134.4 0 0-70.4 67.2-44.8 211.2 25.6 134.4 144 169.6 220.8 169.6 0 0-32 25.6-25.6 70.4 0 0-44.8 25.6-89.6 9.6-44.8-19.2-67.2-64-67.2-64S240 608 195.2 630.4c0 0-12.8 12.8 35.2 35.2 0 0 35.2 54.4 48 86.4 12.8 32 86.4 57.6 156.8 41.6V896s0 9.6-19.2 12.8c-19.2 3.2-19.2 12.8-9.6 12.8h323.2c9.6 0 9.6-9.6-9.6-12.8-19.2-3.2-19.2-12.8-19.2-12.8V723.2c0-44.8-32-70.4-32-70.4 76.8 0 195.2-35.2 220.8-169.6 22.4-147.2-48-214.4-48-214.4z' fill='%23fff'/%3E%3C/svg%3E")}.tab[data-v-13711752]{width:100%;display:flex;margin-bottom:1em;position:relative}@media (max-width:640px){.tab[data-v-13711752]{display:none}}.tab .tab-item[data-v-13711752]{font-size:14px;font-weight:400;width:85px;margin-right:.5em;height:35px;display:flex;align-items:center;justify-content:center;position:relative;top:1px;cursor:pointer;opacity:.3;background-size:20px 20px;background-repeat:no-repeat;background-position:0}.tab .tab-item.active[data-v-13711752]{opacity:1}.tab .tab-item.view[data-v-13711752]{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg class='icon' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cdefs%3E%3Cstyle/%3E%3C/defs%3E%3Cpath d='M512 251.853c192.768 0 358.707 113.1 436.378 276.275H1024c-82.022-202.394-280.218-345.293-512-345.293S82.022 325.735 0 528.128h75.674C153.344 364.954 319.284 251.853 512 251.853zm0 552.55c-192.717 0-358.656-113.05-436.326-276.275H0c82.022 202.445 280.166 345.344 512 345.344s430.029-142.9 512-345.344h-75.674C870.707 691.354 704.768 804.403 512 804.403zM327.834 528.128a184.115 184.115 0 10368.281.051 184.115 184.115 0 00-368.281-.051zm299.315 0a115.2 115.2 0 11-230.298 0 115.2 115.2 0 01230.298 0zm0 0'/%3E%3C/svg%3E")}.tab .tab-item.code[data-v-13711752]{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg class='icon' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cdefs%3E%3Cstyle/%3E%3C/defs%3E%3Cpath d='M549.973 128a42.453 42.453 0 00-42.154 37.76l-75.904 683.136a42.325 42.325 0 1084.266 9.387l75.904-683.179A42.325 42.325 0 00550.016 128zM243.541 286.165a42.539 42.539 0 00-30.208 12.502L30.165 481.835a42.624 42.624 0 000 60.33l183.168 183.168a42.667 42.667 0 0060.331-60.33L120.661 512l153.003-153.003a42.624 42.624 0 00-30.165-72.832zm537.003 0a42.667 42.667 0 00-30.165 72.832L903.339 512 750.336 665.003a42.667 42.667 0 0060.33 60.33l183.169-183.168a42.624 42.624 0 000-60.33L810.667 298.667a42.539 42.539 0 00-30.166-12.502z'/%3E%3C/svg%3E")}.tab .complete-code-url[data-v-13711752]{position:absolute;right:0;top:50%;transform:translateY(-50%);font-size:12px} -------------------------------------------------------------------------------- /docs/css/chat-room.9f37805a.css: -------------------------------------------------------------------------------- 1 | .item[data-v-5b53987b]{display:flex}.item .avatar[data-v-5b53987b]{width:40px;height:40px;border-radius:50%;background:rgba(255,192,203,.2)}@media (max-width:640px){.item .avatar[data-v-5b53987b]{width:30px;height:30px}}.item .avatar img[data-v-5b53987b]{display:block;width:100%;height:100%;border-radius:50%}.item .body[data-v-5b53987b]{flex:1;padding-left:1em;font-size:16px;max-width:560px;word-break:break-word}@media (max-width:640px){.item .body[data-v-5b53987b]{font-size:14px;max-width:unset}}.item .body .name[data-v-5b53987b]{padding-bottom:.2em;font-size:12px}.item .body .content[data-v-5b53987b]{position:relative;color:#000;background-color:#f0f8ff;border-radius:15px;padding:.5em 1em}@media (max-width:640px){.item .body .content[data-v-5b53987b]{padding:.5em}}.item .body .content[data-v-5b53987b]:after{content:"";position:absolute;right:100%;top:10px;width:14px;height:14px;border-width:0;border-style:solid;border-color:transparent;border-bottom-width:10px;border-bottom-color:currentColor;border-radius:0 0 0 32px;color:#f0f8ff}.item.creator[data-v-5b53987b]{transform:rotateX(180deg);direction:rtl;align-items:flex-end}.item.creator .avatar[data-v-5b53987b]{transform:rotateX(180deg)}.item.creator .body[data-v-5b53987b]{transform:rotate(180deg)}.item.creator .text[data-v-5b53987b]{transform:rotateY(180deg);direction:ltr}.editor[data-v-0655a508]{position:relative;font-size:16px}.rich[data-v-0655a508]{border:2px solid;border-color:#696969;border-top:none;width:100%;padding:1em 4em 1em 1em;outline:none;cursor:text;background-color:#fffaf0}.rich[data-v-0655a508]:empty:before{color:#a9a9a9;content:attr(placehoder)}.send[data-v-0655a508]{position:absolute;right:20px;top:15px;width:33px;height:30px;cursor:pointer;background-size:cover;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg class='icon' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cpath d='M0 561.6l366.4 100.8 400-356.8-304 401.6 363.2 107.2 198.4-768zm460.8 416l126.4-192L460.8 760z' fill='%239b4dca'/%3E%3C/svg%3E")}.send[data-v-0655a508]:active{opacity:.7}.send.disabled[data-v-0655a508]{opacity:.3;pointer-events:none}.author-avatar[data-v-0655a508]{display:none}.item[data-v-0655a508]{padding:0}.line[data-v-0655a508]{font-size:12px;margin:0 1em}@media (max-width:640px){.toolbar[data-v-53de24ea]{width:100%;white-space:nowrap;overflow-x:auto}}.toolbar .item[data-v-53de24ea]{padding:0}.toolbar .line[data-v-53de24ea]{margin:0 1em;font-size:12px}.main{margin-top:1em}.empty,.stream{position:relative;width:100%;height:500px;border:2px solid;border-bottom:none;overflow-y:auto;border-color:#696969;display:flex;flex-direction:column-reverse}@media (max-width:640px){.empty,.stream{height:430px}}.empty.overflow,.stream.overflow{flex-direction:column}.empty .stream-item,.stream .stream-item{display:flex;align-items:center;padding:1em}@media (max-width:640px){.empty .stream-item,.stream .stream-item{padding:.5em}}.empty .stream-item.creator,.stream .stream-item.creator{flex-direction:row-reverse}.empty .wrapper{position:absolute;left:0;top:0;bottom:0;right:0;display:flex;justify-content:center;align-items:center;flex-direction:column;color:#bfbfbf}.empty .icon{width:70px;height:70px;background-size:cover;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg class='icon' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cpath d='M915.91 428.28l40.909 6.786 1.773-10.49 8.709-12.937-103.65-69.574-7.776-5.22-7.838-2.919-295.426-109.994-6.927-2.58-6.962 2.309L191.15 338.875l-8.805 2.148-5.705 4.046-123.09 87.058 9.834 13.854 4.493 11.24 39.739-16.354-31.881 44.224 96.369 54.05.107 199.535-2.35 4.283 2.353 1.311.001 2.71h4.86l281.959 157.194 7.696 4.26 2.344 1.277 2.635 1.726 9.419-3.08 8.748-3.42 388.7-159.572.032-9.443V546.374l92.49-35.76-55.189-82.334zM561.218 260.058l245.843 91.539-245.843 92.116V260.058zm-440.216 215.07l40.684-56.453 5.984-8.358 23.44-32.558L454.11 501.38l-42.82 136.613-290.287-162.867zm82.049 251.006l-.1-169.63 226.371 126.99 28.646-91.387.894 276.697-255.811-142.67zM473.818 476.5L225.705 359.87l304.674-100.98v196.376l-56.56 21.234zm373.948 248.116L489.772 871.571l-.956-301.186 45.281 109.166 313.668-121.216v166.281zM551.062 639.89L493.82 501.95l347.098-130.084 83.065 123.893-372.921 144.13z' fill='%23bfbfbf'/%3E%3C/svg%3E")}.header{padding:.5em}.header .finished{font-size:14px;text-align:center;color:#bfbfbf}.header .spinner{font-size:10px;margin:0 auto;text-indent:-9999em;width:15px;height:15px;border-radius:50%;background:#fff;background:linear-gradient(90deg,#ccc 10%,hsla(0,0%,100%,0) 42%);position:relative;-webkit-animation:load3 1.4s linear infinite;animation:load3 1.4s linear infinite;transform:translateZ(0)}.header .spinner:before{width:50%;height:50%;background:#ccc;border-radius:100% 0 0 0;position:absolute;top:0;left:0;content:""}.header .spinner:after{background:#fff;width:75%;height:75%;border-radius:50%;content:"";margin:auto;position:absolute;top:0;left:0;bottom:0;right:0}@-webkit-keyframes load3{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes load3{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}} -------------------------------------------------------------------------------- /docs/css/dynamic-size.9013d8ce.css: -------------------------------------------------------------------------------- 1 | .item-inner .head[data-v-5f14b5b8]{font-weight:500}.item-inner span[data-v-5f14b5b8]:first-child{margin-right:1em}.item-inner .desc[data-v-5f14b5b8]{padding-top:.5em;text-align:justify}.list-dynamic{width:100%;height:500px;border:2px solid;border-radius:3px;overflow-y:auto;border-color:#696969}.list-dynamic .list-item-dynamic{display:flex;align-items:center;padding:1em;border-bottom:1px solid;border-color:#d3d3d3} -------------------------------------------------------------------------------- /docs/css/fixed-size.840ec49f.css: -------------------------------------------------------------------------------- 1 | .item-inner span[data-v-e8597b74]:first-child{margin-right:1em}.list{width:100%;height:500px;border:2px solid;border-radius:3px;overflow-y:auto;border-color:#696969}.list .list-item-fixed{display:flex;align-items:center;padding:0 1em;height:60px;border-bottom:1px solid;border-color:#d3d3d3} -------------------------------------------------------------------------------- /docs/css/horizontal.50e26ffa.css: -------------------------------------------------------------------------------- 1 | .item-inner[data-v-3248155a]{display:flex;align-items:center;flex-direction:column;padding:2em 0}.item-inner .index[data-v-3248155a]{width:100%;text-align:center}.item-inner .size[data-v-3248155a]{text-align:right;color:#a9a9a9;font-size:16px}.list-horizontal{width:100%;border:2px solid;border-radius:3px;overflow-x:auto;border-color:#696969;display:flex}.list-horizontal .wrapper{display:flex;flex-direction:row}.list-horizontal .list-item-horizontal{border-right:1px solid #dfdfdf} -------------------------------------------------------------------------------- /docs/css/infinite-loading.4b238c18.css: -------------------------------------------------------------------------------- 1 | .item-inner .head[data-v-b5a02d2e]{font-weight:500}.item-inner .index[data-v-b5a02d2e]{margin-right:1em}.item-inner .name[data-v-b5a02d2e]{margin-left:1em}.item-inner .desc[data-v-b5a02d2e]{padding-top:.5em;text-align:justify}.result{margin-bottom:1em}.list-infinite{width:100%;height:500px;border:2px solid;border-radius:3px;overflow-y:auto;border-color:#696969;position:relative}.list-infinite .list-item-infinite{display:flex;align-items:center;padding:1em;border-bottom:1px solid;border-color:#d3d3d3}.list-infinite .loader-wrapper{padding:1em}.list-infinite .loader{font-size:10px;margin:0 auto;text-indent:-9999em;width:30px;height:30px;border-radius:50%;background:#fff;background:linear-gradient(90deg,#9b4dca 10%,hsla(0,0%,100%,0) 42%);position:relative;-webkit-animation:load3 1.4s linear infinite;animation:load3 1.4s linear infinite;transform:translateZ(0)}.list-infinite .loader:before{width:50%;height:50%;background:#9b4dca;border-radius:100% 0 0 0;position:absolute;top:0;left:0;content:""}.list-infinite .loader:after{background:#fff;width:75%;height:75%;border-radius:50%;content:"";margin:auto;position:absolute;top:0;left:0;bottom:0;right:0}@-webkit-keyframes load3{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes load3{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}} -------------------------------------------------------------------------------- /docs/css/keep-state.2736386a.css: -------------------------------------------------------------------------------- 1 | .item-inner[data-v-f0c75490]{position:relative;display:flex;align-items:center}.item-inner .index[data-v-f0c75490]{margin-right:1em}.item-inner .name[data-v-f0c75490]{margin-left:1em;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.item-inner .checkbox[data-v-f0c75490]{text-align:center;width:30px;height:30px;border:none;outline:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;margin:0;background-color:#fff;background-repeat:no-repeat;background-position:0;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='-10 -18 100 135'%3E%3Ccircle cx='50' cy='50' r='50' fill='none' stroke='%23ededed' stroke-width='3'/%3E%3C/svg%3E")}.item-inner .checkbox[data-v-f0c75490]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='-10 -18 100 135'%3E%3Ccircle cx='50' cy='50' r='50' fill='none' stroke='%23c28ce2' stroke-width='3'/%3E%3Cpath fill='%239b4dca' d='M72 25L42 71 27 56l-4 4 20 20 34-52z'/%3E%3C/svg%3E")}.title[data-v-5618352e]{margin:3em 0 2em 0}.selects{margin-bottom:1em;font-size:14px}.list-keep{width:100%;height:500px;border:2px solid;border-radius:3px;overflow-y:auto;border-color:#696969}.list-keep .list-item-keep{display:flex;align-items:center;padding:0 1em;height:60px;border-bottom:1px solid;border-color:#d3d3d3} -------------------------------------------------------------------------------- /docs/css/page-mode.bb1117ff.css: -------------------------------------------------------------------------------- 1 | .item-inner .head[data-v-1df8c792]{font-weight:500}.item-inner span[data-v-1df8c792]:first-child{margin-right:1em}.item-inner .desc[data-v-1df8c792]{padding-top:.5em;text-align:justify}.list-page{width:100%;border:2px solid;border-radius:3px;overflow-y:auto;border-color:#696969}.list-page .list-item-page{display:flex;align-items:center;padding:1em;border-bottom:1px solid;border-color:#d3d3d3}.bottom{padding:2em 0} -------------------------------------------------------------------------------- /docs/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangbc/vue-virtual-scroll-list/a4123b7a311d28c70e819812927285e92a43cd64/docs/favicon.png -------------------------------------------------------------------------------- /docs/highlight/theme.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | github.com style (c) Vasily Polovnyov 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | color: #333; 12 | background: #f8f8f8; 13 | } 14 | 15 | .hljs-comment, 16 | .hljs-quote { 17 | color: #998; 18 | font-style: italic; 19 | } 20 | 21 | .hljs-keyword, 22 | .hljs-selector-tag, 23 | .hljs-subst { 24 | color: #333; 25 | font-weight: bold; 26 | } 27 | 28 | .hljs-number, 29 | .hljs-literal, 30 | .hljs-variable, 31 | .hljs-template-variable, 32 | .hljs-tag .hljs-attr { 33 | color: #008080; 34 | } 35 | 36 | .hljs-string, 37 | .hljs-doctag { 38 | color: #d14; 39 | } 40 | 41 | .hljs-title, 42 | .hljs-section, 43 | .hljs-selector-id { 44 | color: #900; 45 | font-weight: bold; 46 | } 47 | 48 | .hljs-subst { 49 | font-weight: normal; 50 | } 51 | 52 | .hljs-type, 53 | .hljs-class .hljs-title { 54 | color: #458; 55 | font-weight: bold; 56 | } 57 | 58 | .hljs-tag, 59 | .hljs-name, 60 | .hljs-attribute { 61 | color: #000080; 62 | font-weight: normal; 63 | } 64 | 65 | .hljs-regexp, 66 | .hljs-link { 67 | color: #009926; 68 | } 69 | 70 | .hljs-symbol, 71 | .hljs-bullet { 72 | color: #990073; 73 | } 74 | 75 | .hljs-built_in, 76 | .hljs-builtin-name { 77 | color: #0086b3; 78 | } 79 | 80 | .hljs-meta { 81 | color: #999; 82 | font-weight: bold; 83 | } 84 | 85 | .hljs-deletion { 86 | background: #fdd; 87 | } 88 | 89 | .hljs-addition { 90 | background: #dfd; 91 | } 92 | 93 | .hljs-emphasis { 94 | font-style: italic; 95 | } 96 | 97 | .hljs-strong { 98 | font-weight: bold; 99 | } -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | vue-virtual-scroll-list
-------------------------------------------------------------------------------- /docs/js/chat-room.c08970e6.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chat-room"],{"498a":function(e,t,n){"use strict";var a=n("23e7"),s=n("58a8").trim,i=n("c8d2");a({target:"String",proto:!0,forced:i("trim")},{trim:function(){return s(this)}})},5254:function(e,t,n){},"6c2b":function(e,t,n){"use strict";var a=n("f2eb"),s=n.n(a);s.a},"6f9a":function(e,t,n){},"808e":function(e,t,n){"use strict";var a=n("5254"),s=n.n(a);s.a},b10c:function(e,t,n){},bd0f:function(e,t,n){"use strict";var a=n("6f9a"),s=n.n(a);s.a},c8d2:function(e,t,n){var a=n("d039"),s=n("5899"),i="​…᠎";e.exports=function(e){return a((function(){return!!s[e]()||i[e]()!=i||s[e].name!==e}))}},c927:function(e,t,n){"use strict";n.d(t,"a",(function(){return i}));n("a15b");var a=n("adf9"),s=n("835c");function i(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:6,n=c[a["Random"].pick([0,1,2])],i=[],o=a["Random"].integer(e,s["a"]?3:t);while(o--)i.push(a["Random"].pick(n));return i.join(". ")+"."}var o=["I messed up tonight I lost another fight","I still mess up but I'll just start again","I keep falling down I keep on hitting the ground","I always get up now to see what's next","Birds don't just fly they fall down and get up","Nobody learns without getting it won","I won't give up no I won't give in","Till I reach the end and then I'll start again","No I won't leave I wanna try everything","I wanna try even though I could fail","I won't give up no I won't give in","Till I reach the end and then I'll start again","No I won't leave I wanna try everything","I wanna try even though I could fail","Look at how far you've come you filled your heart with love","Baby you've done enough that cut your breath","Don't beat yourself up don't need to run so fast","Sometimes we come last but we did our best","I won't give up no I won't give in","Till I reach the end and then I'll start again","No I won't leave I wanna try everything","I wanna try even though I could fail","I won't give up no I won't give in","Till I reach the end and then I'll start again","No I won't leave I wanna try everything","I wanna try even though I could fail","I'll keep on making those new mistakes","I'll keep on making them every day","Those new mistakes"],r=["I will run I will climb I will soar","I'm undefeated","Jumping out of my skin pull the chord","Yeah I believe it","The past is everything we were don't make us who we are","So I'll dream until I make it real and all I see is stars","It's not until you fall that you fly","When your dreams come alive you're unstoppable","Take a shot chase the sun find the beautiful","We will glow in the dark turning dust to gold","And we'll dream it possible","And we'll dream it possible","I will chase I will reach I will fly","Until I'm breaking until I'm breaking","Out of my cage like a bird in the night","I know I'm changing I know I'm changing","In into something big better than before","And if it takes takes a thousand lives","Then it's worth fighting for","It's not until you fall that you fly","When your dreams come alive you're unstoppable","Take a shot chase the sun find the beautiful","We will glow in the dark turning dust to gold","And we'll dream it possible","It possible","From the bottom to the top","We're sparking wild fire's","Never quit and never stop","The rest of our lives","From the bottom to the top","We're sparking wild fire's","Never quit and never stop","It's not until you fall that you fly","When your dreams come alive you're unstoppable","Take a shot chase the sun find the beautiful","We will glow in the dark turning dust to gold","And we'll dream it possible","And we'll dream it possible"],l=["I can almost see it","That dream I'm dreamin' but","There's a voice inside my head saying","You'll never reach it","Every step I'm taking","Every move I make feels","Lost with no direction","My faith is shakin","But I I gotta keep tryin","Gotta keep my head held high","There's always gonna be another mountain","I'm always gonna wanna make it move","Always gonna be an uphill battle","Sometimes I'm gonna have to lose","Ain't about how fast I get there","Ain't about what's waitin on the other side","It's the climb","The struggles I'm facing","The chances I'm taking","Sometimes might knock me down but","No I'm not breaking","I may not know it","But these are the moments that","I'm gonna remember most yeah","Just gotta keep going","And I I gotta be strong","Just keep pushing on 'cause","There's always gonna be another mountain","I'm always gonna wanna make it move","Always gonna be an uphill battle","But Sometimes I'm gonna have to lose","Ain't about how fast I get there","Ain't about what's waitin on the other side","It's the climb","Yeah-yeah","There's always gonna be another mountain","I'm always gonna wanna make it move","Always gonna be an uphill battle","Sometimes you're gonna have to lose","Ain't about how fast I get there","Ain't about what's waitin on the other side","It's the climb","Yeah-yeah-yea","Keep on moving","Keep climbing","Keep the faith","Baby It's all about","It's all about the climb","Keep your faith","Whoa O Whoa"],c=[o,r,l]},cbe9:function(e,t,n){"use strict";n.r(t);var a=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"example"},[n("github-corner"),n("tool-bar"),n("div",{staticClass:"main"},[n("div",{staticClass:"list-container"},[n("virtual-list",{directives:[{name:"show",rawName:"v-show",value:!!e.messages.length,expression:"!!messages.length"}],ref:"vsl",staticClass:"stream scroll-touch",class:{overflow:e.overflow},attrs:{"data-key":"sid","data-sources":e.messages,"data-component":e.messageComponent,"estimate-size":100,"item-class":"stream-item","item-class-add":e.addItemClass},on:{resized:e.onItemRendered,totop:e.onTotop}},[n("div",{directives:[{name:"show",rawName:"v-show",value:e.overflow,expression:"overflow"}],staticClass:"header",attrs:{slot:"header"},slot:"header"},[n("div",{directives:[{name:"show",rawName:"v-show",value:!e.finished,expression:"!finished"}],staticClass:"spinner"}),n("div",{directives:[{name:"show",rawName:"v-show",value:e.finished,expression:"finished"}],staticClass:"finished"},[e._v("No more data")])])]),n("div",{directives:[{name:"show",rawName:"v-show",value:!e.messages.length,expression:"!messages.length"}],staticClass:"empty"},[e._m(0)])],1),n("massage-eidtor",{attrs:{send:e.sendRandomMessage,received:e.receivedRandomMessage},on:{send:e.onSendMessage}})],1)],1)},s=[function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"wrapper"},[n("div",{staticClass:"icon"}),n("div",{staticClass:"tips"},[e._v("No chats")])])}],i=(n("99af"),n("13d5"),function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"item",class:{creator:e.source.isCreator}},[n("div",{staticClass:"avatar"},[n("img",{attrs:{src:e.source.user.avatar}})]),n("div",{staticClass:"body"},[e.source.isCreator?e._e():n("div",{staticClass:"name"},[e._v(e._s(e.source.user.name))]),n("div",{staticClass:"content"},[n("div",{staticClass:"text"},[e._v(e._s(e.source.content))])])])])}),o=[],r={name:"chat-room-item",props:{source:{type:Object,default:function(){return{sid:"",user:{},content:"",images:[]}}}}},l=r,c=(n("bd0f"),n("2877")),u=Object(c["a"])(l,i,o,!1,null,"5b53987b",null),h=u.exports,d=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"editor"},[n("div",{ref:"rich",staticClass:"rich",attrs:{contenteditable:"true",placehoder:e.placehoder},on:{keydown:e.onKeydown,input:e.onInput,paste:e.onPaste}}),n("div",{staticClass:"send",class:{disabled:e.disabledSend},on:{mousedown:function(e){e.preventDefault()},click:e.eventSend}}),n("img",{staticClass:"author-avatar",attrs:{src:"https://avatars1.githubusercontent.com/u/6846113",alt:"preload-avatar"}}),n("div",{staticClass:"auto"},[n("button",{staticClass:"button button-clear item",on:{mousedown:function(e){e.preventDefault()},click:e.eventClickMockReceived}},[e._v("received one random")]),n("span",{staticClass:"line"},[e._v("|")]),n("button",{staticClass:"button button-clear item",on:{mousedown:function(e){e.preventDefault()},click:e.eventClickMockSend}},[e._v("send one random")])])])},m=[],v=(n("498a"),n("835c")),f=(n("d81d"),n("d3b7"),n("adf9")),g=n("c927"),p=(n("b0c0"),function(){return"https://avatars1.githubusercontent.com/u/".concat(f["Random"].integer(1e4,99999))}),w=function(){return{name:f["Random"].name(),avatar:p()}},b=0,I=500;function y(){return"sid-".concat(b++)}function k(){return{user:{},sid:"",content:"",images:[],isCreator:!1}}function C(e,t){return new Promise((function(n){b>=I?n([]):setTimeout((function(){var t=[];while(e--){var a=k();a.user=w(),a.content=Object(g["a"])(),a.sid=y(),t.push(a)}n(t)}),t?f["Random"].pick([300,500,800]):0)}))}function T(e){return e.map((function(e){return e.sid}))}var S={EMPTY:"EMPTY",PAGES:"PAGES",FEW:"FEW"};function _(e){try{sessionStorage.setItem("LOAD_TYPES",e)}catch(t){console.error(t)}}function E(){try{return sessionStorage.getItem("LOAD_TYPES")||S.PAGES}catch(e){return console.error(e),S.EMPTY}}var $=function(e){var t=k();return t.user={name:"tangbc",avatar:"https://avatars1.githubusercontent.com/u/6846113"},t.sid=y(),t.content=e,t.isCreator=!0,t},A={name:"editor",props:{send:{type:Function},received:{type:Function}},data:function(){return{disabledSend:!0,placehoder:v["a"]?"Input message here":"Input message here and press enter to send"}},mounted:function(){this.$refs.rich&&!v["a"]&&this.$refs.rich.focus()},methods:{onKeydown:function(e){13===e.keyCode&&(this.checkDisable(),this.eventSend(),e.preventDefault())},onInput:function(){this.checkDisable()},onPaste:function(e){e.preventDefault();var t=(e.originalEvent||e).clipboardData.getData("text/plain");document.execCommand("insertText",!1,t)},checkDisable:function(){this.$refs.rich&&(this.disabledSend=!(this.$refs.rich.textContent||"").trim(""))},eventSend:function(){if(this.$refs.rich&&!this.disabledSend){var e=(this.$refs.rich.textContent||"").trim(),t=$(e);this.$refs.rich.innerHTML="",this.checkDisable(),this.$emit("send",t)}},eventClickMockReceived:function(){this.received()},eventClickMockSend:function(){this.send($(""))}}},x=A,P=(n("e7e9"),Object(c["a"])(x,d,m,!1,null,"0655a508",null)),F=P.exports,M=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"toolbar"},[n("button",{staticClass:"button button-clear item",on:{click:e.eventClickPages}},[e._v("reload with pages")]),n("span",{staticClass:"line"},[e._v("|")]),n("button",{staticClass:"button button-clear item",on:{click:e.eventClickFew}},[e._v("reload with few")]),n("span",{staticClass:"line"},[e._v("|")]),n("button",{staticClass:"button button-clear item",on:{click:e.eventClickEmpty}},[e._v("reload with empty")])])},R=[],O={name:"tool-bar",methods:{eventClickEmpty:function(){_(S.EMPTY),this.reload()},eventClickPages:function(){_(S.PAGES),this.reload()},eventClickFew:function(){_(S.FEW),this.reload()},reload:function(){window.location.reload()}}},N=O,D=(n("6c2b"),Object(c["a"])(N,M,R,!1,null,"53de24ea",null)),W=D.exports,L={name:"chat-room",components:{"massage-eidtor":F,"tool-bar":W},data:function(){return{finished:!1,messages:[],messageComponent:h,overflow:!1}},created:function(){var e=this,t=E();this.param={pageSize:t===S.FEW?2:10,isFirstPageReady:!1,isFetching:!1},this.finished=t!==S.PAGES,t!==S.EMPTY&&C(this.param.pageSize).then((function(t){e.messages=e.messages.concat(t)}))},methods:{onTotop:function(){var e=this;E()!==S.PAGES||this.param.isFetching||(this.param.isFetching=!0,C(this.param.pageSize,!0).then((function(t){if(t.length){var n=T(t);e.messages=t.concat(e.messages),e.$nextTick((function(){var t=e.$refs.vsl,a=n.reduce((function(n,a){var s="string"===typeof n?t.getSize(n):n;return s+e.$refs.vsl.getSize(a)}));e.setVirtualListToOffset(a),e.param.isFetching=!1}))}else e.finished=!0})))},onItemRendered:function(){this.$refs.vsl&&(!this.param.isFirstPageReady&&this.$refs.vsl.getSizes()>=this.param.pageSize&&(this.param.isFirstPageReady=!0,this.setVirtualListToBottom()),this.checkOverFlow())},onSendMessage:function(e){var t=this;this.messages.push(e),this.$nextTick((function(){t.setVirtualListToBottom()}))},addItemClass:function(e){return this.messages[e]&&this.messages[e].isCreator?"creator":""},checkOverFlow:function(){var e=this.$refs.vsl;e&&(this.overflow=e.getScrollSize()>e.getClientSize())},receivedRandomMessage:function(){var e=this;C(1).then((function(t){e.messages=e.messages.concat(t),e.$nextTick((function(){e.setVirtualListToBottom()}))}))},sendRandomMessage:function(e){e.content=Object(g["a"])(),this.onSendMessage(e)},setVirtualListToIndex:function(e){this.$refs.vsl&&this.$refs.vsl.scrollToIndex(e)},setVirtualListToOffset:function(e){this.$refs.vsl&&this.$refs.vsl.scrollToOffset(e)},setVirtualListToBottom:function(){this.$refs.vsl&&this.$refs.vsl.scrollToBottom()}}},z=L,B=(n("808e"),Object(c["a"])(z,a,s,!1,null,null,null));t["default"]=B.exports},e7e9:function(e,t,n){"use strict";var a=n("b10c"),s=n.n(a);s.a},f2eb:function(e,t,n){}}]); -------------------------------------------------------------------------------- /docs/js/dynamic-size.3003e8a2.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["dynamic-size"],{2146:function(e,t,n){"use strict";var a=n("fe3f"),i=n.n(a);i.a},"960e":function(e,t,n){"use strict";n.r(t);var a=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"example"},[n("github-corner"),n("introduction",{attrs:{description:"The size of each item is dynamic,\n you don't have to care about size, it will calculate automatic,\n but you have to make sure that there's an unique id for every array data."}}),n("div",{staticClass:"example-content"},[n("tab",{on:{"tab-change":e.onTabChange}}),n("div",{directives:[{name:"show",rawName:"v-show",value:e.isShowView,expression:"isShowView"}]},[n("virtual-list",{staticClass:"list-dynamic scroll-touch",attrs:{"data-key":"id","data-sources":e.items,"data-component":e.itemComponent,"estimate-size":80,"item-class":"list-item-dynamic"}})],1),n("codeblock",{directives:[{name:"show",rawName:"v-show",value:!e.isShowView,expression:"!isShowView"}]})],1)],1)},i=[],o=(n("b0c0"),function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"item-inner"},[n("div",{staticClass:"head"},[n("span",[e._v("# "+e._s(e.source.index))]),n("span",[e._v(e._s(e.source.name))])]),n("div",{staticClass:"desc"},[e._v(e._s(e.source.desc))])])}),s=[],l={name:"dynamic-size-item",props:{source:{type:Object,default:function(){return{}}}}},r=l,h=(n("ed29"),n("2877")),u=Object(h["a"])(r,o,s,!1,null,"5f14b5b8",null),m=u.exports,d=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",[n("code-high-light",{attrs:{type:"html",code:e.html}}),n("code-high-light",{attrs:{type:"js",code:e.js}})],1)},c=[],w='\n\n',g="\nimport Item from './Item'\nconst items = [\n {\n id: 'unique-id-xxx',\n ...\n },\n ....\n]\n\nexport default {\n ...\n data () {\n return {\n items: items,\n itemComponent: Item,\n }\n }\n ...\n}\n",I={name:"dynamic-size-code",data:function(){return{html:w,js:g}}},y=I,b=Object(h["a"])(y,d,c,!1,null,null,null),v=b.exports,p=n("adf9"),f=n("c927"),k=n("c57d"),T=n("b95e"),A=1e4,x=[],S=A;while(S--){var j=A-S;x.push({index:j,name:p["Random"].name(),id:Object(k["a"])(j),desc:Object(f["a"])()})}var C={name:"dynamic-size",components:{codeblock:v},data:function(){return{total:A.toLocaleString(),items:x,itemComponent:m,isShowView:T["a"]===T["b"].VIEW}},methods:{onTabChange:function(e){this.isShowView=e===T["b"].VIEW}}},W=C,_=(n("2146"),Object(h["a"])(W,a,i,!1,null,null,null));t["default"]=_.exports},c57d:function(e,t,n){"use strict";n("99af"),n("d3b7"),n("25f0");t["a"]=function(e){return"".concat(e,"$").concat(Math.random().toString(16).substr(9))}},c927:function(e,t,n){"use strict";n.d(t,"a",(function(){return o}));n("a15b");var a=n("adf9"),i=n("835c");function o(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:6,n=h[a["Random"].pick([0,1,2])],o=[],s=a["Random"].integer(e,i["a"]?3:t);while(s--)o.push(a["Random"].pick(n));return o.join(". ")+"."}var s=["I messed up tonight I lost another fight","I still mess up but I'll just start again","I keep falling down I keep on hitting the ground","I always get up now to see what's next","Birds don't just fly they fall down and get up","Nobody learns without getting it won","I won't give up no I won't give in","Till I reach the end and then I'll start again","No I won't leave I wanna try everything","I wanna try even though I could fail","I won't give up no I won't give in","Till I reach the end and then I'll start again","No I won't leave I wanna try everything","I wanna try even though I could fail","Look at how far you've come you filled your heart with love","Baby you've done enough that cut your breath","Don't beat yourself up don't need to run so fast","Sometimes we come last but we did our best","I won't give up no I won't give in","Till I reach the end and then I'll start again","No I won't leave I wanna try everything","I wanna try even though I could fail","I won't give up no I won't give in","Till I reach the end and then I'll start again","No I won't leave I wanna try everything","I wanna try even though I could fail","I'll keep on making those new mistakes","I'll keep on making them every day","Those new mistakes"],l=["I will run I will climb I will soar","I'm undefeated","Jumping out of my skin pull the chord","Yeah I believe it","The past is everything we were don't make us who we are","So I'll dream until I make it real and all I see is stars","It's not until you fall that you fly","When your dreams come alive you're unstoppable","Take a shot chase the sun find the beautiful","We will glow in the dark turning dust to gold","And we'll dream it possible","And we'll dream it possible","I will chase I will reach I will fly","Until I'm breaking until I'm breaking","Out of my cage like a bird in the night","I know I'm changing I know I'm changing","In into something big better than before","And if it takes takes a thousand lives","Then it's worth fighting for","It's not until you fall that you fly","When your dreams come alive you're unstoppable","Take a shot chase the sun find the beautiful","We will glow in the dark turning dust to gold","And we'll dream it possible","It possible","From the bottom to the top","We're sparking wild fire's","Never quit and never stop","The rest of our lives","From the bottom to the top","We're sparking wild fire's","Never quit and never stop","It's not until you fall that you fly","When your dreams come alive you're unstoppable","Take a shot chase the sun find the beautiful","We will glow in the dark turning dust to gold","And we'll dream it possible","And we'll dream it possible"],r=["I can almost see it","That dream I'm dreamin' but","There's a voice inside my head saying","You'll never reach it","Every step I'm taking","Every move I make feels","Lost with no direction","My faith is shakin","But I I gotta keep tryin","Gotta keep my head held high","There's always gonna be another mountain","I'm always gonna wanna make it move","Always gonna be an uphill battle","Sometimes I'm gonna have to lose","Ain't about how fast I get there","Ain't about what's waitin on the other side","It's the climb","The struggles I'm facing","The chances I'm taking","Sometimes might knock me down but","No I'm not breaking","I may not know it","But these are the moments that","I'm gonna remember most yeah","Just gotta keep going","And I I gotta be strong","Just keep pushing on 'cause","There's always gonna be another mountain","I'm always gonna wanna make it move","Always gonna be an uphill battle","But Sometimes I'm gonna have to lose","Ain't about how fast I get there","Ain't about what's waitin on the other side","It's the climb","Yeah-yeah","There's always gonna be another mountain","I'm always gonna wanna make it move","Always gonna be an uphill battle","Sometimes you're gonna have to lose","Ain't about how fast I get there","Ain't about what's waitin on the other side","It's the climb","Yeah-yeah-yea","Keep on moving","Keep climbing","Keep the faith","Baby It's all about","It's all about the climb","Keep your faith","Whoa O Whoa"],h=[s,l,r]},ed28:function(e,t,n){},ed29:function(e,t,n){"use strict";var a=n("ed28"),i=n.n(a);i.a},fe3f:function(e,t,n){}}]); -------------------------------------------------------------------------------- /docs/js/fixed-size.1b9763e9.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["fixed-size"],{"19e7":function(e,t,n){},3188:function(e,t,n){},c227:function(e,t,n){"use strict";n.r(t);var i=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"example"},[n("github-corner"),n("introduction",{attrs:{description:"The size of each item is equal."}}),n("div",{staticClass:"example-content"},[n("tab",{on:{"tab-change":e.onTabChange}}),n("div",{directives:[{name:"show",rawName:"v-show",value:e.isShowView,expression:"isShowView"}]},[n("virtual-list",{staticClass:"list scroll-touch",attrs:{"data-key":"id","data-sources":e.items,"data-component":e.itemComponent,"estimate-size":50,"item-class":"list-item-fixed"}})],1),n("codeblock",{directives:[{name:"show",rawName:"v-show",value:!e.isShowView,expression:"!isShowView"}]})],1)],1)},s=[],a=(n("b0c0"),function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"item-inner"},[n("span",[e._v("# "+e._s(e.source.index))]),n("span",[e._v(e._s(e.source.name))])])}),o=[],c={name:"fix-size-item",props:{source:{type:Object,default:function(){return{}}}}},r=c,l=(n("d16c"),n("2877")),u=Object(l["a"])(r,a,o,!1,null,"e8597b74",null),d=u.exports,m=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",[n("code-high-light",{attrs:{type:"html",code:e.html}}),n("code-high-light",{attrs:{type:"js",code:e.js}})],1)},h=[],f='\n\n',p="\nimport Item from './Item'\nconst items = [\n {\n id: 'unique-id-xxx',\n ...\n },\n ....\n]\n\nexport default {\n ...\n data () {\n return {\n items: items,\n itemComponent: Item,\n }\n }\n ...\n}\n",w={name:"fix-size-code",data:function(){return{html:f,js:p}}},v=w,b=Object(l["a"])(v,m,h,!1,null,null,null),x=b.exports,g=n("adf9"),C=n("c57d"),_=n("b95e"),j=1e4,S=[],V=j;while(V--){var z=j-V;S.push({index:z,name:g["Random"].name(),id:Object(C["a"])(z)})}var k={name:"fix-size",components:{codeblock:x},data:function(){return{total:j.toLocaleString(),items:S,itemComponent:d,isShowView:_["a"]===_["b"].VIEW}},methods:{onTabChange:function(e){this.isShowView=e===_["b"].VIEW}}},y=k,E=(n("e3d2"),Object(l["a"])(y,i,s,!1,null,null,null));t["default"]=E.exports},c57d:function(e,t,n){"use strict";n("99af"),n("d3b7"),n("25f0");t["a"]=function(e){return"".concat(e,"$").concat(Math.random().toString(16).substr(9))}},d16c:function(e,t,n){"use strict";var i=n("19e7"),s=n.n(i);s.a},e3d2:function(e,t,n){"use strict";var i=n("3188"),s=n.n(i);s.a}}]); -------------------------------------------------------------------------------- /docs/js/horizontal.ad2c5dae.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["horizontal"],{a0c6:function(t,e,i){"use strict";var a=i("baec"),n=i.n(a);n.a},a2ab:function(t,e,i){"use strict";i.r(e);var a=function(){var t=this,e=t.$createElement,i=t._self._c||e;return i("div",{staticClass:"example"},[i("github-corner"),i("introduction",{attrs:{description:"Set direction as horizontal, and also can use wrap-class, item-class to help you layout items in horizontal."}}),i("div",{staticClass:"example-content"},[i("tab",{on:{"tab-change":t.onTabChange}}),i("div",{directives:[{name:"show",rawName:"v-show",value:t.isShowView,expression:"isShowView"}]},[i("virtual-list",{staticClass:"list-horizontal scroll-touch",attrs:{"data-key":"id","data-sources":t.items,"data-component":t.itemComponent,"estimate-size":110,direction:"horizontal","wrap-class":"wrapper","item-class":"list-item-horizontal"}})],1),i("codeblock",{directives:[{name:"show",rawName:"v-show",value:!t.isShowView,expression:"!isShowView"}]})],1)],1)},n=[],o=function(){var t=this,e=t.$createElement,i=t._self._c||e;return i("div",{staticClass:"item-inner",style:{width:t.source.size+"px"}},[i("div",{staticClass:"index"},[t._v("# "+t._s(t.source.index))]),i("div",{staticClass:"size"},[t._v(t._s(t.source.size))])])},s=[],c={name:"horizontal-item",props:{source:{type:Object,default:function(){return{}}}}},r=c,l=(i("a0c6"),i("2877")),d=Object(l["a"])(r,o,s,!1,null,"3248155a",null),u=d.exports,h=function(){var t=this,e=t.$createElement,i=t._self._c||e;return i("div",[t.html?i("code-high-light",{attrs:{type:"html",code:t.html}}):t._e(),t.js?i("code-high-light",{attrs:{type:"js",code:t.js}}):t._e()],1)},m=[],p='\n\n',w="",v={name:"horizontal-code",data:function(){return{html:p,js:w}}},f=v,b=Object(l["a"])(f,h,m,!1,null,null,null),z=b.exports,_=i("adf9"),x=i("c57d"),C=i("b95e"),g=1e4,j=[60,80,100,150,180],k=[],y=g;while(y--){var S=g-y;k.push({index:S,id:Object(x["a"])(S),size:_["Random"].pick(j)})}var V={name:"horizontal",components:{codeblock:z},data:function(){return{items:k,itemComponent:u,isShowView:C["a"]===C["b"].VIEW}},methods:{onTabChange:function(t){this.isShowView=t===C["b"].VIEW}}},E=V,O=(i("f597"),Object(l["a"])(E,a,n,!1,null,null,null));e["default"]=O.exports},baec:function(t,e,i){},c57d:function(t,e,i){"use strict";i("99af"),i("d3b7"),i("25f0");e["a"]=function(t){return"".concat(t,"$").concat(Math.random().toString(16).substr(9))}},ca79:function(t,e,i){},f597:function(t,e,i){"use strict";var a=i("ca79"),n=i.n(a);n.a}}]); -------------------------------------------------------------------------------- /docs/js/infinite-loading.ce8d4372.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["infinite-loading"],{"077d":function(t,e,n){"use strict";n.r(e);var a=function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"example"},[n("github-corner"),n("introduction",{attrs:{description:"Use v-on:tobottom to listen scroll reach bottom, add a footer slot as loading, then append next parts data into data-sources array."}}),n("div",{staticClass:"example-content"},[n("tab",{on:{"tab-change":t.onTabChange}}),n("div",{staticClass:"result"},[t._v("Items count: "+t._s(t.items.length)+".")]),n("div",{directives:[{name:"show",rawName:"v-show",value:t.isShowView,expression:"isShowView"}]},[n("virtual-list",{staticClass:"list-infinite scroll-touch",attrs:{"data-key":"id","data-sources":t.items,"data-component":t.itemComponent,"estimate-size":70,"item-class":"list-item-infinite","footer-class":"loader-wrapper"},on:{totop:t.onScrollToTop,tobottom:t.onScrollToBottom}},[n("div",{staticClass:"loader",attrs:{slot:"footer"},slot:"footer"})])],1),n("codeblock",{directives:[{name:"show",rawName:"v-show",value:!t.isShowView,expression:"!isShowView"}]})],1)],1)},o=[],i=(n("99af"),n("b0c0"),function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"item-inner"},[n("div",{staticClass:"head"},[n("span",{staticClass:"index"},[t._v("# "+t._s(t.source.index))]),n("span",{staticClass:"name"},[t._v(t._s(t.source.name))])]),n("div",{staticClass:"desc"},[t._v(t._s(t.source.desc))])])}),s=[],l={name:"infinite-loading-item",props:{source:{type:Object,default:function(){return{}}}}},r=l,h=(n("d316"),n("2877")),u=Object(h["a"])(r,i,s,!1,null,"b5a02d2e",null),d=u.exports,c=function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",[n("code-high-light",{attrs:{type:"html",code:t.html}}),n("code-high-light",{attrs:{type:"js",code:t.js}})],1)},m=[],g='\n\n
Loading ...
\n
\n',w="\nexport default {\n data () {\n return {\n itemComponent: Item,\n items: getPageData(pageSize, 0)\n }\n },\n\n methods: {\n onScrollToBottom () {\n this.items = this.items.concat(getPageData(pageSize, pageNum))\n }\n }\n}\n",I={name:"infinite-loading-code",data:function(){return{html:g,js:w}}},p=I,f=Object(h["a"])(p,c,m,!1,null,null,null),v=f.exports,b=n("adf9"),y=n("c927"),k=n("c57d"),T=n("b95e"),S=function(t,e){for(var n=[],a=0;a0&&void 0!==arguments[0]?arguments[0]:1,e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:6,n=h[a["Random"].pick([0,1,2])],i=[],s=a["Random"].integer(t,o["a"]?3:e);while(s--)i.push(a["Random"].pick(n));return i.join(". ")+"."}var s=["I messed up tonight I lost another fight","I still mess up but I'll just start again","I keep falling down I keep on hitting the ground","I always get up now to see what's next","Birds don't just fly they fall down and get up","Nobody learns without getting it won","I won't give up no I won't give in","Till I reach the end and then I'll start again","No I won't leave I wanna try everything","I wanna try even though I could fail","I won't give up no I won't give in","Till I reach the end and then I'll start again","No I won't leave I wanna try everything","I wanna try even though I could fail","Look at how far you've come you filled your heart with love","Baby you've done enough that cut your breath","Don't beat yourself up don't need to run so fast","Sometimes we come last but we did our best","I won't give up no I won't give in","Till I reach the end and then I'll start again","No I won't leave I wanna try everything","I wanna try even though I could fail","I won't give up no I won't give in","Till I reach the end and then I'll start again","No I won't leave I wanna try everything","I wanna try even though I could fail","I'll keep on making those new mistakes","I'll keep on making them every day","Those new mistakes"],l=["I will run I will climb I will soar","I'm undefeated","Jumping out of my skin pull the chord","Yeah I believe it","The past is everything we were don't make us who we are","So I'll dream until I make it real and all I see is stars","It's not until you fall that you fly","When your dreams come alive you're unstoppable","Take a shot chase the sun find the beautiful","We will glow in the dark turning dust to gold","And we'll dream it possible","And we'll dream it possible","I will chase I will reach I will fly","Until I'm breaking until I'm breaking","Out of my cage like a bird in the night","I know I'm changing I know I'm changing","In into something big better than before","And if it takes takes a thousand lives","Then it's worth fighting for","It's not until you fall that you fly","When your dreams come alive you're unstoppable","Take a shot chase the sun find the beautiful","We will glow in the dark turning dust to gold","And we'll dream it possible","It possible","From the bottom to the top","We're sparking wild fire's","Never quit and never stop","The rest of our lives","From the bottom to the top","We're sparking wild fire's","Never quit and never stop","It's not until you fall that you fly","When your dreams come alive you're unstoppable","Take a shot chase the sun find the beautiful","We will glow in the dark turning dust to gold","And we'll dream it possible","And we'll dream it possible"],r=["I can almost see it","That dream I'm dreamin' but","There's a voice inside my head saying","You'll never reach it","Every step I'm taking","Every move I make feels","Lost with no direction","My faith is shakin","But I I gotta keep tryin","Gotta keep my head held high","There's always gonna be another mountain","I'm always gonna wanna make it move","Always gonna be an uphill battle","Sometimes I'm gonna have to lose","Ain't about how fast I get there","Ain't about what's waitin on the other side","It's the climb","The struggles I'm facing","The chances I'm taking","Sometimes might knock me down but","No I'm not breaking","I may not know it","But these are the moments that","I'm gonna remember most yeah","Just gotta keep going","And I I gotta be strong","Just keep pushing on 'cause","There's always gonna be another mountain","I'm always gonna wanna make it move","Always gonna be an uphill battle","But Sometimes I'm gonna have to lose","Ain't about how fast I get there","Ain't about what's waitin on the other side","It's the climb","Yeah-yeah","There's always gonna be another mountain","I'm always gonna wanna make it move","Always gonna be an uphill battle","Sometimes you're gonna have to lose","Ain't about how fast I get there","Ain't about what's waitin on the other side","It's the climb","Yeah-yeah-yea","Keep on moving","Keep climbing","Keep the faith","Baby It's all about","It's all about the climb","Keep your faith","Whoa O Whoa"],h=[s,l,r]},d316:function(t,e,n){"use strict";var a=n("9f22"),o=n.n(a);o.a}}]); -------------------------------------------------------------------------------- /docs/js/keep-state.08df8235.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["keep-state"],{"0786":function(e,t,n){"use strict";n.r(t);var a=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"example"},[n("github-corner"),n("introduction",{attrs:{description:"Maintaining item component inner state is a trouble here, recommend to use only props data."}}),n("div",{staticClass:"example-content"},[n("tab",{on:{"tab-change":e.onTabChange}}),n("div",{directives:[{name:"show",rawName:"v-show",value:e.isShowView,expression:"isShowView"}],staticClass:"selects"},[e._v(e._s(e.selectNames))]),n("div",{directives:[{name:"show",rawName:"v-show",value:e.isShowView,expression:"isShowView"}]},[n("virtual-list",{staticClass:"list-keep scroll-touch",attrs:{"data-key":"id","data-sources":e.items,"data-component":e.itemComponent,"estimate-size":60,"item-class":"list-item-keep"}})],1),n("codeblock",{directives:[{name:"show",rawName:"v-show",value:!e.isShowView,expression:"!isShowView"}]})],1)],1)},i=[],s=(n("4de4"),n("7db0"),n("d81d"),n("b0c0"),function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"item-inner"},[n("span",{staticClass:"index"},[e._v("# "+e._s(e.source.index))]),n("input",{staticClass:"checkbox",attrs:{type:"checkbox"},domProps:{checked:e.source.checked},on:{change:e.onChange}}),n("span",{staticClass:"name",on:{click:e.onClickName}},[e._v(e._s(e.source.name))])])}),c=[],o=(n("99af"),{methods:{dispatch:function(e,t){var n=this.$parent||this.$root,a=n.$options.name;while(n&&(!a||a!==e))n=n.$parent,n&&(a=n.$options.name);if(n){for(var i=arguments.length,s=new Array(i>2?i-2:0),c=2;c1?arguments[1]:void 0)}}),s(o)},b2e6:function(e,t,n){"use strict";var a=n("4fc0"),i=n.n(a);i.a},c57d:function(e,t,n){"use strict";n("99af"),n("d3b7"),n("25f0");t["a"]=function(e){return"".concat(e,"$").concat(Math.random().toString(16).substr(9))}},cb52:function(e,t,n){"use strict";var a=n("64d5"),i=n.n(a);i.a}}]); -------------------------------------------------------------------------------- /docs/js/page-mode.7fc3339d.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["page-mode"],{"2fcf":function(e,t,n){"use strict";var a=n("7fe2"),o=n.n(a);o.a},"6e70":function(e,t,n){"use strict";n.r(t);var a=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"example"},[n("github-corner"),n("introduction",{attrs:{description:"In page-mode virtual list using global document to scroll through the list."}}),n("div",{staticClass:"example-content"},[n("div",[n("virtual-list",{ref:"vsl",staticClass:"list-page scroll-touch",attrs:{"data-key":"id","data-sources":e.items,"data-component":e.itemComponent,"estimate-size":135,"item-class":"list-item-page","page-mode":!0},on:{totop:e.totop,tobottom:e.tobottom}}),e._m(0)],1)])],1)},o=[function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"bottom"},[n("h2",[e._v("This is page footer")])])}],i=(n("b0c0"),function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"item-inner"},[n("div",{staticClass:"head"},[n("span",[e._v("# "+e._s(e.source.index))]),n("span",[e._v(e._s(e.source.name))])]),n("div",{staticClass:"desc"},[e._v(e._s(e.source.desc))])])}),s=[],l={name:"page-mode-item",props:{source:{type:Object,default:function(){return{}}}}},r=l,h=(n("2fcf"),n("2877")),u=Object(h["a"])(r,i,s,!1,null,"1df8c792",null),m=u.exports,d=n("adf9"),c=n("c927"),g=n("c57d"),w=1e3,I=[],p=w;while(p--){var f=w-p;I.push({index:f,name:d["Random"].name(),id:Object(g["a"])(f),desc:Object(c["a"])()})}var b={name:"page-mode",components:{},data:function(){return{items:I,itemComponent:m}},methods:{totop:function(){console.log("reach totop")},tobottom:function(){console.log("reach tobottom")}}},v=b,y=(n("e5d8"),Object(h["a"])(v,a,o,!1,null,null,null));t["default"]=y.exports},"7fe2":function(e,t,n){},c57d:function(e,t,n){"use strict";n("99af"),n("d3b7"),n("25f0");t["a"]=function(e){return"".concat(e,"$").concat(Math.random().toString(16).substr(9))}},c927:function(e,t,n){"use strict";n.d(t,"a",(function(){return i}));n("a15b");var a=n("adf9"),o=n("835c");function i(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:6,n=h[a["Random"].pick([0,1,2])],i=[],s=a["Random"].integer(e,o["a"]?3:t);while(s--)i.push(a["Random"].pick(n));return i.join(". ")+"."}var s=["I messed up tonight I lost another fight","I still mess up but I'll just start again","I keep falling down I keep on hitting the ground","I always get up now to see what's next","Birds don't just fly they fall down and get up","Nobody learns without getting it won","I won't give up no I won't give in","Till I reach the end and then I'll start again","No I won't leave I wanna try everything","I wanna try even though I could fail","I won't give up no I won't give in","Till I reach the end and then I'll start again","No I won't leave I wanna try everything","I wanna try even though I could fail","Look at how far you've come you filled your heart with love","Baby you've done enough that cut your breath","Don't beat yourself up don't need to run so fast","Sometimes we come last but we did our best","I won't give up no I won't give in","Till I reach the end and then I'll start again","No I won't leave I wanna try everything","I wanna try even though I could fail","I won't give up no I won't give in","Till I reach the end and then I'll start again","No I won't leave I wanna try everything","I wanna try even though I could fail","I'll keep on making those new mistakes","I'll keep on making them every day","Those new mistakes"],l=["I will run I will climb I will soar","I'm undefeated","Jumping out of my skin pull the chord","Yeah I believe it","The past is everything we were don't make us who we are","So I'll dream until I make it real and all I see is stars","It's not until you fall that you fly","When your dreams come alive you're unstoppable","Take a shot chase the sun find the beautiful","We will glow in the dark turning dust to gold","And we'll dream it possible","And we'll dream it possible","I will chase I will reach I will fly","Until I'm breaking until I'm breaking","Out of my cage like a bird in the night","I know I'm changing I know I'm changing","In into something big better than before","And if it takes takes a thousand lives","Then it's worth fighting for","It's not until you fall that you fly","When your dreams come alive you're unstoppable","Take a shot chase the sun find the beautiful","We will glow in the dark turning dust to gold","And we'll dream it possible","It possible","From the bottom to the top","We're sparking wild fire's","Never quit and never stop","The rest of our lives","From the bottom to the top","We're sparking wild fire's","Never quit and never stop","It's not until you fall that you fly","When your dreams come alive you're unstoppable","Take a shot chase the sun find the beautiful","We will glow in the dark turning dust to gold","And we'll dream it possible","And we'll dream it possible"],r=["I can almost see it","That dream I'm dreamin' but","There's a voice inside my head saying","You'll never reach it","Every step I'm taking","Every move I make feels","Lost with no direction","My faith is shakin","But I I gotta keep tryin","Gotta keep my head held high","There's always gonna be another mountain","I'm always gonna wanna make it move","Always gonna be an uphill battle","Sometimes I'm gonna have to lose","Ain't about how fast I get there","Ain't about what's waitin on the other side","It's the climb","The struggles I'm facing","The chances I'm taking","Sometimes might knock me down but","No I'm not breaking","I may not know it","But these are the moments that","I'm gonna remember most yeah","Just gotta keep going","And I I gotta be strong","Just keep pushing on 'cause","There's always gonna be another mountain","I'm always gonna wanna make it move","Always gonna be an uphill battle","But Sometimes I'm gonna have to lose","Ain't about how fast I get there","Ain't about what's waitin on the other side","It's the climb","Yeah-yeah","There's always gonna be another mountain","I'm always gonna wanna make it move","Always gonna be an uphill battle","Sometimes you're gonna have to lose","Ain't about how fast I get there","Ain't about what's waitin on the other side","It's the climb","Yeah-yeah-yea","Keep on moving","Keep climbing","Keep the faith","Baby It's all about","It's all about the climb","Keep your faith","Whoa O Whoa"],h=[s,l,r]},cc99:function(e,t,n){},e5d8:function(e,t,n){"use strict";var a=n("cc99"),o=n.n(a);o.a}}]); -------------------------------------------------------------------------------- /docs/milligram.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Milligram v1.3.0 3 | * https://milligram.github.io 4 | * 5 | * Copyright (c) 2017 CJ Patoilo 6 | * Licensed under the MIT license 7 | */ 8 | 9 | *, 10 | *:after, 11 | *:before { 12 | box-sizing: inherit; 13 | } 14 | 15 | html { 16 | box-sizing: border-box; 17 | font-size: 62.5%; 18 | } 19 | 20 | body { 21 | color: #606c76; 22 | font-family: 'Roboto', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif; 23 | font-size: 1.6em; 24 | font-weight: 300; 25 | letter-spacing: .01em; 26 | line-height: 1.6; 27 | } 28 | 29 | blockquote { 30 | border-left: 0.3rem solid #d1d1d1; 31 | margin-left: 0; 32 | margin-right: 0; 33 | padding: 1rem 1.5rem; 34 | } 35 | 36 | blockquote *:last-child { 37 | margin-bottom: 0; 38 | } 39 | 40 | .button, 41 | button, 42 | input[type='button'], 43 | input[type='reset'], 44 | input[type='submit'] { 45 | background-color: #9b4dca; 46 | border: 0.1rem solid #9b4dca; 47 | border-radius: .4rem; 48 | color: #fff; 49 | cursor: pointer; 50 | display: inline-block; 51 | font-size: 1.1rem; 52 | font-weight: 700; 53 | height: 3.8rem; 54 | letter-spacing: .1rem; 55 | line-height: 3.8rem; 56 | padding: 0 3.0rem; 57 | text-align: center; 58 | text-decoration: none; 59 | text-transform: uppercase; 60 | white-space: nowrap; 61 | } 62 | 63 | .button:focus, .button:hover, 64 | button:focus, 65 | button:hover, 66 | input[type='button']:focus, 67 | input[type='button']:hover, 68 | input[type='reset']:focus, 69 | input[type='reset']:hover, 70 | input[type='submit']:focus, 71 | input[type='submit']:hover { 72 | background-color: #606c76; 73 | border-color: #606c76; 74 | color: #fff; 75 | outline: 0; 76 | } 77 | 78 | .button[disabled], 79 | button[disabled], 80 | input[type='button'][disabled], 81 | input[type='reset'][disabled], 82 | input[type='submit'][disabled] { 83 | cursor: default; 84 | opacity: .5; 85 | } 86 | 87 | .button[disabled]:focus, .button[disabled]:hover, 88 | button[disabled]:focus, 89 | button[disabled]:hover, 90 | input[type='button'][disabled]:focus, 91 | input[type='button'][disabled]:hover, 92 | input[type='reset'][disabled]:focus, 93 | input[type='reset'][disabled]:hover, 94 | input[type='submit'][disabled]:focus, 95 | input[type='submit'][disabled]:hover { 96 | background-color: #9b4dca; 97 | border-color: #9b4dca; 98 | } 99 | 100 | .button.button-outline, 101 | button.button-outline, 102 | input[type='button'].button-outline, 103 | input[type='reset'].button-outline, 104 | input[type='submit'].button-outline { 105 | background-color: transparent; 106 | color: #9b4dca; 107 | } 108 | 109 | .button.button-outline:focus, .button.button-outline:hover, 110 | button.button-outline:focus, 111 | button.button-outline:hover, 112 | input[type='button'].button-outline:focus, 113 | input[type='button'].button-outline:hover, 114 | input[type='reset'].button-outline:focus, 115 | input[type='reset'].button-outline:hover, 116 | input[type='submit'].button-outline:focus, 117 | input[type='submit'].button-outline:hover { 118 | background-color: transparent; 119 | border-color: #606c76; 120 | color: #606c76; 121 | } 122 | 123 | .button.button-outline[disabled]:focus, .button.button-outline[disabled]:hover, 124 | button.button-outline[disabled]:focus, 125 | button.button-outline[disabled]:hover, 126 | input[type='button'].button-outline[disabled]:focus, 127 | input[type='button'].button-outline[disabled]:hover, 128 | input[type='reset'].button-outline[disabled]:focus, 129 | input[type='reset'].button-outline[disabled]:hover, 130 | input[type='submit'].button-outline[disabled]:focus, 131 | input[type='submit'].button-outline[disabled]:hover { 132 | border-color: inherit; 133 | color: #9b4dca; 134 | } 135 | 136 | .button.button-clear, 137 | button.button-clear, 138 | input[type='button'].button-clear, 139 | input[type='reset'].button-clear, 140 | input[type='submit'].button-clear { 141 | background-color: transparent; 142 | border-color: transparent; 143 | color: #9b4dca; 144 | } 145 | 146 | .button.button-clear:focus, .button.button-clear:hover, 147 | button.button-clear:focus, 148 | button.button-clear:hover, 149 | input[type='button'].button-clear:focus, 150 | input[type='button'].button-clear:hover, 151 | input[type='reset'].button-clear:focus, 152 | input[type='reset'].button-clear:hover, 153 | input[type='submit'].button-clear:focus, 154 | input[type='submit'].button-clear:hover { 155 | background-color: transparent; 156 | border-color: transparent; 157 | color: #606c76; 158 | } 159 | 160 | .button.button-clear[disabled]:focus, .button.button-clear[disabled]:hover, 161 | button.button-clear[disabled]:focus, 162 | button.button-clear[disabled]:hover, 163 | input[type='button'].button-clear[disabled]:focus, 164 | input[type='button'].button-clear[disabled]:hover, 165 | input[type='reset'].button-clear[disabled]:focus, 166 | input[type='reset'].button-clear[disabled]:hover, 167 | input[type='submit'].button-clear[disabled]:focus, 168 | input[type='submit'].button-clear[disabled]:hover { 169 | color: #9b4dca; 170 | } 171 | 172 | code { 173 | background: #f4f5f6; 174 | border-radius: .4rem; 175 | font-size: 86%; 176 | margin: 0 .2rem; 177 | padding: .2rem .5rem; 178 | white-space: nowrap; 179 | } 180 | 181 | pre { 182 | background: #f4f5f6; 183 | border-left: 0.3rem solid #9b4dca; 184 | overflow-y: hidden; 185 | } 186 | 187 | pre > code { 188 | border-radius: 0; 189 | display: block; 190 | padding: 1rem 1.5rem; 191 | white-space: pre; 192 | } 193 | 194 | hr { 195 | border: 0; 196 | border-top: 0.1rem solid #f4f5f6; 197 | margin: 3.0rem 0; 198 | } 199 | 200 | input[type='email'], 201 | input[type='number'], 202 | input[type='password'], 203 | input[type='search'], 204 | input[type='tel'], 205 | input[type='text'], 206 | input[type='url'], 207 | textarea, 208 | select { 209 | -webkit-appearance: none; 210 | -moz-appearance: none; 211 | appearance: none; 212 | background-color: transparent; 213 | border: 0.1rem solid #d1d1d1; 214 | border-radius: .4rem; 215 | box-shadow: none; 216 | box-sizing: inherit; 217 | height: 3.8rem; 218 | padding: .6rem 1.0rem; 219 | width: 100%; 220 | } 221 | 222 | input[type='email']:focus, 223 | input[type='number']:focus, 224 | input[type='password']:focus, 225 | input[type='search']:focus, 226 | input[type='tel']:focus, 227 | input[type='text']:focus, 228 | input[type='url']:focus, 229 | textarea:focus, 230 | select:focus { 231 | border-color: #9b4dca; 232 | outline: 0; 233 | } 234 | 235 | select { 236 | background: url('data:image/svg+xml;utf8,') center right no-repeat; 237 | padding-right: 3.0rem; 238 | } 239 | 240 | select:focus { 241 | background-image: url('data:image/svg+xml;utf8,'); 242 | } 243 | 244 | textarea { 245 | min-height: 6.5rem; 246 | } 247 | 248 | label, 249 | legend { 250 | display: block; 251 | font-size: 1.6rem; 252 | font-weight: 700; 253 | margin-bottom: .5rem; 254 | } 255 | 256 | fieldset { 257 | border-width: 0; 258 | padding: 0; 259 | } 260 | 261 | input[type='checkbox'], 262 | input[type='radio'] { 263 | display: inline; 264 | } 265 | 266 | .label-inline { 267 | display: inline-block; 268 | font-weight: normal; 269 | margin-left: .5rem; 270 | } 271 | 272 | .container { 273 | margin: 0 auto; 274 | max-width: 112.0rem; 275 | padding: 0 2.0rem; 276 | position: relative; 277 | width: 100%; 278 | } 279 | 280 | .row { 281 | display: flex; 282 | flex-direction: column; 283 | padding: 0; 284 | width: 100%; 285 | } 286 | 287 | .row.row-no-padding { 288 | padding: 0; 289 | } 290 | 291 | .row.row-no-padding > .column { 292 | padding: 0; 293 | } 294 | 295 | .row.row-wrap { 296 | flex-wrap: wrap; 297 | } 298 | 299 | .row.row-top { 300 | align-items: flex-start; 301 | } 302 | 303 | .row.row-bottom { 304 | align-items: flex-end; 305 | } 306 | 307 | .row.row-center { 308 | align-items: center; 309 | } 310 | 311 | .row.row-stretch { 312 | align-items: stretch; 313 | } 314 | 315 | .row.row-baseline { 316 | align-items: baseline; 317 | } 318 | 319 | .row .column { 320 | display: block; 321 | flex: 1 1 auto; 322 | margin-left: 0; 323 | max-width: 100%; 324 | width: 100%; 325 | } 326 | 327 | .row .column.column-offset-10 { 328 | margin-left: 10%; 329 | } 330 | 331 | .row .column.column-offset-20 { 332 | margin-left: 20%; 333 | } 334 | 335 | .row .column.column-offset-25 { 336 | margin-left: 25%; 337 | } 338 | 339 | .row .column.column-offset-33, .row .column.column-offset-34 { 340 | margin-left: 33.3333%; 341 | } 342 | 343 | .row .column.column-offset-50 { 344 | margin-left: 50%; 345 | } 346 | 347 | .row .column.column-offset-66, .row .column.column-offset-67 { 348 | margin-left: 66.6666%; 349 | } 350 | 351 | .row .column.column-offset-75 { 352 | margin-left: 75%; 353 | } 354 | 355 | .row .column.column-offset-80 { 356 | margin-left: 80%; 357 | } 358 | 359 | .row .column.column-offset-90 { 360 | margin-left: 90%; 361 | } 362 | 363 | .row .column.column-10 { 364 | flex: 0 0 10%; 365 | max-width: 10%; 366 | } 367 | 368 | .row .column.column-20 { 369 | flex: 0 0 20%; 370 | max-width: 20%; 371 | } 372 | 373 | .row .column.column-25 { 374 | flex: 0 0 25%; 375 | max-width: 25%; 376 | } 377 | 378 | .row .column.column-33, .row .column.column-34 { 379 | flex: 0 0 33.3333%; 380 | max-width: 33.3333%; 381 | } 382 | 383 | .row .column.column-40 { 384 | flex: 0 0 40%; 385 | max-width: 40%; 386 | } 387 | 388 | .row .column.column-50 { 389 | flex: 0 0 50%; 390 | max-width: 50%; 391 | } 392 | 393 | .row .column.column-60 { 394 | flex: 0 0 60%; 395 | max-width: 60%; 396 | } 397 | 398 | .row .column.column-66, .row .column.column-67 { 399 | flex: 0 0 66.6666%; 400 | max-width: 66.6666%; 401 | } 402 | 403 | .row .column.column-75 { 404 | flex: 0 0 75%; 405 | max-width: 75%; 406 | } 407 | 408 | .row .column.column-80 { 409 | flex: 0 0 80%; 410 | max-width: 80%; 411 | } 412 | 413 | .row .column.column-90 { 414 | flex: 0 0 90%; 415 | max-width: 90%; 416 | } 417 | 418 | .row .column .column-top { 419 | align-self: flex-start; 420 | } 421 | 422 | .row .column .column-bottom { 423 | align-self: flex-end; 424 | } 425 | 426 | .row .column .column-center { 427 | -ms-grid-row-align: center; 428 | align-self: center; 429 | } 430 | 431 | @media (min-width: 40rem) { 432 | .row { 433 | flex-direction: row; 434 | margin-left: -1.0rem; 435 | width: calc(100% + 2.0rem); 436 | } 437 | .row .column { 438 | margin-bottom: inherit; 439 | padding: 0 1.0rem; 440 | } 441 | } 442 | 443 | a { 444 | color: #9b4dca; 445 | text-decoration: none; 446 | } 447 | 448 | a:focus, a:hover { 449 | color: #606c76; 450 | } 451 | 452 | dl, 453 | ol, 454 | ul { 455 | list-style: none; 456 | margin-top: 0; 457 | padding-left: 0; 458 | } 459 | 460 | dl dl, 461 | dl ol, 462 | dl ul, 463 | ol dl, 464 | ol ol, 465 | ol ul, 466 | ul dl, 467 | ul ol, 468 | ul ul { 469 | font-size: 90%; 470 | margin: 1.5rem 0 1.5rem 3.0rem; 471 | } 472 | 473 | ol { 474 | list-style: decimal inside; 475 | } 476 | 477 | ul { 478 | list-style: circle inside; 479 | } 480 | 481 | .button, 482 | button, 483 | dd, 484 | dt, 485 | li { 486 | margin-bottom: 1.0rem; 487 | } 488 | 489 | fieldset, 490 | input, 491 | select, 492 | textarea { 493 | margin-bottom: 1.5rem; 494 | } 495 | 496 | blockquote, 497 | dl, 498 | figure, 499 | form, 500 | ol, 501 | p, 502 | pre, 503 | table, 504 | ul { 505 | margin-bottom: 2.5rem; 506 | } 507 | 508 | table { 509 | border-spacing: 0; 510 | width: 100%; 511 | } 512 | 513 | td, 514 | th { 515 | border-bottom: 0.1rem solid #e1e1e1; 516 | padding: 1.2rem 1.5rem; 517 | text-align: left; 518 | } 519 | 520 | td:first-child, 521 | th:first-child { 522 | padding-left: 0; 523 | } 524 | 525 | td:last-child, 526 | th:last-child { 527 | padding-right: 0; 528 | } 529 | 530 | b, 531 | strong { 532 | font-weight: bold; 533 | } 534 | 535 | p { 536 | margin-top: 0; 537 | } 538 | 539 | h1, 540 | h2, 541 | h3, 542 | h4, 543 | h5, 544 | h6 { 545 | font-weight: 300; 546 | letter-spacing: -.1rem; 547 | margin-bottom: 2.0rem; 548 | margin-top: 0; 549 | } 550 | 551 | h1 { 552 | font-size: 4.6rem; 553 | line-height: 1.2; 554 | } 555 | 556 | h2 { 557 | font-size: 3.6rem; 558 | line-height: 1.25; 559 | } 560 | 561 | h3 { 562 | font-size: 2.8rem; 563 | line-height: 1.3; 564 | } 565 | 566 | h4 { 567 | font-size: 2.2rem; 568 | letter-spacing: -.08rem; 569 | line-height: 1.35; 570 | } 571 | 572 | h5 { 573 | font-size: 1.8rem; 574 | letter-spacing: -.05rem; 575 | line-height: 1.5; 576 | } 577 | 578 | h6 { 579 | font-size: 1.6rem; 580 | letter-spacing: 0; 581 | line-height: 1.4; 582 | } 583 | 584 | img { 585 | max-width: 100%; 586 | } 587 | 588 | .clearfix:after { 589 | clear: both; 590 | content: ' '; 591 | display: table; 592 | } 593 | 594 | .float-left { 595 | float: left; 596 | } 597 | 598 | .float-right { 599 | float: right; 600 | } 601 | -------------------------------------------------------------------------------- /example/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs-example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "npm run serve", 7 | "serve": "vue-cli-service serve", 8 | "build": "vue-cli-service build", 9 | "lint": "vue-cli-service lint" 10 | }, 11 | "dependencies": { 12 | "core-js": "^3.6.4", 13 | "vue-github-button": "^1.1.2", 14 | "vue-router": "^3.1.6" 15 | }, 16 | "devDependencies": { 17 | "@vue/cli-plugin-babel": "~4.3.0", 18 | "@vue/cli-plugin-eslint": "~4.3.0", 19 | "@vue/cli-plugin-router": "^4.3.0", 20 | "@vue/cli-service": "~4.3.0", 21 | "babel-eslint": "^10.1.0", 22 | "eslint": "^6.7.2", 23 | "eslint-plugin-vue": "^6.2.2", 24 | "less": "^3.11.1", 25 | "less-loader": "^5.0.0", 26 | "raw-loader": "^4.0.0", 27 | "vue-template-compiler": "^2.6.11" 28 | }, 29 | "eslintConfig": { 30 | "root": true, 31 | "env": { 32 | "node": true 33 | }, 34 | "extends": [ 35 | "plugin:vue/essential", 36 | "eslint:recommended" 37 | ], 38 | "parserOptions": { 39 | "parser": "babel-eslint" 40 | }, 41 | "rules": {} 42 | }, 43 | "browserslist": [ 44 | "> 1%", 45 | "last 2 versions", 46 | "not dead" 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /example/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangbc/vue-virtual-scroll-list/a4123b7a311d28c70e819812927285e92a43cd64/example/public/favicon.png -------------------------------------------------------------------------------- /example/public/highlight/theme.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | github.com style (c) Vasily Polovnyov 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | color: #333; 12 | background: #f8f8f8; 13 | } 14 | 15 | .hljs-comment, 16 | .hljs-quote { 17 | color: #998; 18 | font-style: italic; 19 | } 20 | 21 | .hljs-keyword, 22 | .hljs-selector-tag, 23 | .hljs-subst { 24 | color: #333; 25 | font-weight: bold; 26 | } 27 | 28 | .hljs-number, 29 | .hljs-literal, 30 | .hljs-variable, 31 | .hljs-template-variable, 32 | .hljs-tag .hljs-attr { 33 | color: #008080; 34 | } 35 | 36 | .hljs-string, 37 | .hljs-doctag { 38 | color: #d14; 39 | } 40 | 41 | .hljs-title, 42 | .hljs-section, 43 | .hljs-selector-id { 44 | color: #900; 45 | font-weight: bold; 46 | } 47 | 48 | .hljs-subst { 49 | font-weight: normal; 50 | } 51 | 52 | .hljs-type, 53 | .hljs-class .hljs-title { 54 | color: #458; 55 | font-weight: bold; 56 | } 57 | 58 | .hljs-tag, 59 | .hljs-name, 60 | .hljs-attribute { 61 | color: #000080; 62 | font-weight: normal; 63 | } 64 | 65 | .hljs-regexp, 66 | .hljs-link { 67 | color: #009926; 68 | } 69 | 70 | .hljs-symbol, 71 | .hljs-bullet { 72 | color: #990073; 73 | } 74 | 75 | .hljs-built_in, 76 | .hljs-builtin-name { 77 | color: #0086b3; 78 | } 79 | 80 | .hljs-meta { 81 | color: #999; 82 | font-weight: bold; 83 | } 84 | 85 | .hljs-deletion { 86 | background: #fdd; 87 | } 88 | 89 | .hljs-addition { 90 | background: #dfd; 91 | } 92 | 93 | .hljs-emphasis { 94 | font-style: italic; 95 | } 96 | 97 | .hljs-strong { 98 | font-weight: bold; 99 | } -------------------------------------------------------------------------------- /example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | vue-virtual-scroll-list 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /example/public/milligram.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Milligram v1.3.0 3 | * https://milligram.github.io 4 | * 5 | * Copyright (c) 2017 CJ Patoilo 6 | * Licensed under the MIT license 7 | */ 8 | 9 | *, 10 | *:after, 11 | *:before { 12 | box-sizing: inherit; 13 | } 14 | 15 | html { 16 | box-sizing: border-box; 17 | font-size: 62.5%; 18 | } 19 | 20 | body { 21 | color: #606c76; 22 | font-family: 'Roboto', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif; 23 | font-size: 1.6em; 24 | font-weight: 300; 25 | letter-spacing: .01em; 26 | line-height: 1.6; 27 | } 28 | 29 | blockquote { 30 | border-left: 0.3rem solid #d1d1d1; 31 | margin-left: 0; 32 | margin-right: 0; 33 | padding: 1rem 1.5rem; 34 | } 35 | 36 | blockquote *:last-child { 37 | margin-bottom: 0; 38 | } 39 | 40 | .button, 41 | button, 42 | input[type='button'], 43 | input[type='reset'], 44 | input[type='submit'] { 45 | background-color: #9b4dca; 46 | border: 0.1rem solid #9b4dca; 47 | border-radius: .4rem; 48 | color: #fff; 49 | cursor: pointer; 50 | display: inline-block; 51 | font-size: 1.1rem; 52 | font-weight: 700; 53 | height: 3.8rem; 54 | letter-spacing: .1rem; 55 | line-height: 3.8rem; 56 | padding: 0 3.0rem; 57 | text-align: center; 58 | text-decoration: none; 59 | text-transform: uppercase; 60 | white-space: nowrap; 61 | } 62 | 63 | .button:focus, .button:hover, 64 | button:focus, 65 | button:hover, 66 | input[type='button']:focus, 67 | input[type='button']:hover, 68 | input[type='reset']:focus, 69 | input[type='reset']:hover, 70 | input[type='submit']:focus, 71 | input[type='submit']:hover { 72 | background-color: #606c76; 73 | border-color: #606c76; 74 | color: #fff; 75 | outline: 0; 76 | } 77 | 78 | .button[disabled], 79 | button[disabled], 80 | input[type='button'][disabled], 81 | input[type='reset'][disabled], 82 | input[type='submit'][disabled] { 83 | cursor: default; 84 | opacity: .5; 85 | } 86 | 87 | .button[disabled]:focus, .button[disabled]:hover, 88 | button[disabled]:focus, 89 | button[disabled]:hover, 90 | input[type='button'][disabled]:focus, 91 | input[type='button'][disabled]:hover, 92 | input[type='reset'][disabled]:focus, 93 | input[type='reset'][disabled]:hover, 94 | input[type='submit'][disabled]:focus, 95 | input[type='submit'][disabled]:hover { 96 | background-color: #9b4dca; 97 | border-color: #9b4dca; 98 | } 99 | 100 | .button.button-outline, 101 | button.button-outline, 102 | input[type='button'].button-outline, 103 | input[type='reset'].button-outline, 104 | input[type='submit'].button-outline { 105 | background-color: transparent; 106 | color: #9b4dca; 107 | } 108 | 109 | .button.button-outline:focus, .button.button-outline:hover, 110 | button.button-outline:focus, 111 | button.button-outline:hover, 112 | input[type='button'].button-outline:focus, 113 | input[type='button'].button-outline:hover, 114 | input[type='reset'].button-outline:focus, 115 | input[type='reset'].button-outline:hover, 116 | input[type='submit'].button-outline:focus, 117 | input[type='submit'].button-outline:hover { 118 | background-color: transparent; 119 | border-color: #606c76; 120 | color: #606c76; 121 | } 122 | 123 | .button.button-outline[disabled]:focus, .button.button-outline[disabled]:hover, 124 | button.button-outline[disabled]:focus, 125 | button.button-outline[disabled]:hover, 126 | input[type='button'].button-outline[disabled]:focus, 127 | input[type='button'].button-outline[disabled]:hover, 128 | input[type='reset'].button-outline[disabled]:focus, 129 | input[type='reset'].button-outline[disabled]:hover, 130 | input[type='submit'].button-outline[disabled]:focus, 131 | input[type='submit'].button-outline[disabled]:hover { 132 | border-color: inherit; 133 | color: #9b4dca; 134 | } 135 | 136 | .button.button-clear, 137 | button.button-clear, 138 | input[type='button'].button-clear, 139 | input[type='reset'].button-clear, 140 | input[type='submit'].button-clear { 141 | background-color: transparent; 142 | border-color: transparent; 143 | color: #9b4dca; 144 | } 145 | 146 | .button.button-clear:focus, .button.button-clear:hover, 147 | button.button-clear:focus, 148 | button.button-clear:hover, 149 | input[type='button'].button-clear:focus, 150 | input[type='button'].button-clear:hover, 151 | input[type='reset'].button-clear:focus, 152 | input[type='reset'].button-clear:hover, 153 | input[type='submit'].button-clear:focus, 154 | input[type='submit'].button-clear:hover { 155 | background-color: transparent; 156 | border-color: transparent; 157 | color: #606c76; 158 | } 159 | 160 | .button.button-clear[disabled]:focus, .button.button-clear[disabled]:hover, 161 | button.button-clear[disabled]:focus, 162 | button.button-clear[disabled]:hover, 163 | input[type='button'].button-clear[disabled]:focus, 164 | input[type='button'].button-clear[disabled]:hover, 165 | input[type='reset'].button-clear[disabled]:focus, 166 | input[type='reset'].button-clear[disabled]:hover, 167 | input[type='submit'].button-clear[disabled]:focus, 168 | input[type='submit'].button-clear[disabled]:hover { 169 | color: #9b4dca; 170 | } 171 | 172 | code { 173 | background: #f4f5f6; 174 | border-radius: .4rem; 175 | font-size: 86%; 176 | margin: 0 .2rem; 177 | padding: .2rem .5rem; 178 | white-space: nowrap; 179 | } 180 | 181 | pre { 182 | background: #f4f5f6; 183 | border-left: 0.3rem solid #9b4dca; 184 | overflow-y: hidden; 185 | } 186 | 187 | pre > code { 188 | border-radius: 0; 189 | display: block; 190 | padding: 1rem 1.5rem; 191 | white-space: pre; 192 | } 193 | 194 | hr { 195 | border: 0; 196 | border-top: 0.1rem solid #f4f5f6; 197 | margin: 3.0rem 0; 198 | } 199 | 200 | input[type='email'], 201 | input[type='number'], 202 | input[type='password'], 203 | input[type='search'], 204 | input[type='tel'], 205 | input[type='text'], 206 | input[type='url'], 207 | textarea, 208 | select { 209 | -webkit-appearance: none; 210 | -moz-appearance: none; 211 | appearance: none; 212 | background-color: transparent; 213 | border: 0.1rem solid #d1d1d1; 214 | border-radius: .4rem; 215 | box-shadow: none; 216 | box-sizing: inherit; 217 | height: 3.8rem; 218 | padding: .6rem 1.0rem; 219 | width: 100%; 220 | } 221 | 222 | input[type='email']:focus, 223 | input[type='number']:focus, 224 | input[type='password']:focus, 225 | input[type='search']:focus, 226 | input[type='tel']:focus, 227 | input[type='text']:focus, 228 | input[type='url']:focus, 229 | textarea:focus, 230 | select:focus { 231 | border-color: #9b4dca; 232 | outline: 0; 233 | } 234 | 235 | select { 236 | background: url('data:image/svg+xml;utf8,') center right no-repeat; 237 | padding-right: 3.0rem; 238 | } 239 | 240 | select:focus { 241 | background-image: url('data:image/svg+xml;utf8,'); 242 | } 243 | 244 | textarea { 245 | min-height: 6.5rem; 246 | } 247 | 248 | label, 249 | legend { 250 | display: block; 251 | font-size: 1.6rem; 252 | font-weight: 700; 253 | margin-bottom: .5rem; 254 | } 255 | 256 | fieldset { 257 | border-width: 0; 258 | padding: 0; 259 | } 260 | 261 | input[type='checkbox'], 262 | input[type='radio'] { 263 | display: inline; 264 | } 265 | 266 | .label-inline { 267 | display: inline-block; 268 | font-weight: normal; 269 | margin-left: .5rem; 270 | } 271 | 272 | .container { 273 | margin: 0 auto; 274 | max-width: 112.0rem; 275 | padding: 0 2.0rem; 276 | position: relative; 277 | width: 100%; 278 | } 279 | 280 | .row { 281 | display: flex; 282 | flex-direction: column; 283 | padding: 0; 284 | width: 100%; 285 | } 286 | 287 | .row.row-no-padding { 288 | padding: 0; 289 | } 290 | 291 | .row.row-no-padding > .column { 292 | padding: 0; 293 | } 294 | 295 | .row.row-wrap { 296 | flex-wrap: wrap; 297 | } 298 | 299 | .row.row-top { 300 | align-items: flex-start; 301 | } 302 | 303 | .row.row-bottom { 304 | align-items: flex-end; 305 | } 306 | 307 | .row.row-center { 308 | align-items: center; 309 | } 310 | 311 | .row.row-stretch { 312 | align-items: stretch; 313 | } 314 | 315 | .row.row-baseline { 316 | align-items: baseline; 317 | } 318 | 319 | .row .column { 320 | display: block; 321 | flex: 1 1 auto; 322 | margin-left: 0; 323 | max-width: 100%; 324 | width: 100%; 325 | } 326 | 327 | .row .column.column-offset-10 { 328 | margin-left: 10%; 329 | } 330 | 331 | .row .column.column-offset-20 { 332 | margin-left: 20%; 333 | } 334 | 335 | .row .column.column-offset-25 { 336 | margin-left: 25%; 337 | } 338 | 339 | .row .column.column-offset-33, .row .column.column-offset-34 { 340 | margin-left: 33.3333%; 341 | } 342 | 343 | .row .column.column-offset-50 { 344 | margin-left: 50%; 345 | } 346 | 347 | .row .column.column-offset-66, .row .column.column-offset-67 { 348 | margin-left: 66.6666%; 349 | } 350 | 351 | .row .column.column-offset-75 { 352 | margin-left: 75%; 353 | } 354 | 355 | .row .column.column-offset-80 { 356 | margin-left: 80%; 357 | } 358 | 359 | .row .column.column-offset-90 { 360 | margin-left: 90%; 361 | } 362 | 363 | .row .column.column-10 { 364 | flex: 0 0 10%; 365 | max-width: 10%; 366 | } 367 | 368 | .row .column.column-20 { 369 | flex: 0 0 20%; 370 | max-width: 20%; 371 | } 372 | 373 | .row .column.column-25 { 374 | flex: 0 0 25%; 375 | max-width: 25%; 376 | } 377 | 378 | .row .column.column-33, .row .column.column-34 { 379 | flex: 0 0 33.3333%; 380 | max-width: 33.3333%; 381 | } 382 | 383 | .row .column.column-40 { 384 | flex: 0 0 40%; 385 | max-width: 40%; 386 | } 387 | 388 | .row .column.column-50 { 389 | flex: 0 0 50%; 390 | max-width: 50%; 391 | } 392 | 393 | .row .column.column-60 { 394 | flex: 0 0 60%; 395 | max-width: 60%; 396 | } 397 | 398 | .row .column.column-66, .row .column.column-67 { 399 | flex: 0 0 66.6666%; 400 | max-width: 66.6666%; 401 | } 402 | 403 | .row .column.column-75 { 404 | flex: 0 0 75%; 405 | max-width: 75%; 406 | } 407 | 408 | .row .column.column-80 { 409 | flex: 0 0 80%; 410 | max-width: 80%; 411 | } 412 | 413 | .row .column.column-90 { 414 | flex: 0 0 90%; 415 | max-width: 90%; 416 | } 417 | 418 | .row .column .column-top { 419 | align-self: flex-start; 420 | } 421 | 422 | .row .column .column-bottom { 423 | align-self: flex-end; 424 | } 425 | 426 | .row .column .column-center { 427 | -ms-grid-row-align: center; 428 | align-self: center; 429 | } 430 | 431 | @media (min-width: 40rem) { 432 | .row { 433 | flex-direction: row; 434 | margin-left: -1.0rem; 435 | width: calc(100% + 2.0rem); 436 | } 437 | .row .column { 438 | margin-bottom: inherit; 439 | padding: 0 1.0rem; 440 | } 441 | } 442 | 443 | a { 444 | color: #9b4dca; 445 | text-decoration: none; 446 | } 447 | 448 | a:focus, a:hover { 449 | color: #606c76; 450 | } 451 | 452 | dl, 453 | ol, 454 | ul { 455 | list-style: none; 456 | margin-top: 0; 457 | padding-left: 0; 458 | } 459 | 460 | dl dl, 461 | dl ol, 462 | dl ul, 463 | ol dl, 464 | ol ol, 465 | ol ul, 466 | ul dl, 467 | ul ol, 468 | ul ul { 469 | font-size: 90%; 470 | margin: 1.5rem 0 1.5rem 3.0rem; 471 | } 472 | 473 | ol { 474 | list-style: decimal inside; 475 | } 476 | 477 | ul { 478 | list-style: circle inside; 479 | } 480 | 481 | .button, 482 | button, 483 | dd, 484 | dt, 485 | li { 486 | margin-bottom: 1.0rem; 487 | } 488 | 489 | fieldset, 490 | input, 491 | select, 492 | textarea { 493 | margin-bottom: 1.5rem; 494 | } 495 | 496 | blockquote, 497 | dl, 498 | figure, 499 | form, 500 | ol, 501 | p, 502 | pre, 503 | table, 504 | ul { 505 | margin-bottom: 2.5rem; 506 | } 507 | 508 | table { 509 | border-spacing: 0; 510 | width: 100%; 511 | } 512 | 513 | td, 514 | th { 515 | border-bottom: 0.1rem solid #e1e1e1; 516 | padding: 1.2rem 1.5rem; 517 | text-align: left; 518 | } 519 | 520 | td:first-child, 521 | th:first-child { 522 | padding-left: 0; 523 | } 524 | 525 | td:last-child, 526 | th:last-child { 527 | padding-right: 0; 528 | } 529 | 530 | b, 531 | strong { 532 | font-weight: bold; 533 | } 534 | 535 | p { 536 | margin-top: 0; 537 | } 538 | 539 | h1, 540 | h2, 541 | h3, 542 | h4, 543 | h5, 544 | h6 { 545 | font-weight: 300; 546 | letter-spacing: -.1rem; 547 | margin-bottom: 2.0rem; 548 | margin-top: 0; 549 | } 550 | 551 | h1 { 552 | font-size: 4.6rem; 553 | line-height: 1.2; 554 | } 555 | 556 | h2 { 557 | font-size: 3.6rem; 558 | line-height: 1.25; 559 | } 560 | 561 | h3 { 562 | font-size: 2.8rem; 563 | line-height: 1.3; 564 | } 565 | 566 | h4 { 567 | font-size: 2.2rem; 568 | letter-spacing: -.08rem; 569 | line-height: 1.35; 570 | } 571 | 572 | h5 { 573 | font-size: 1.8rem; 574 | letter-spacing: -.05rem; 575 | line-height: 1.5; 576 | } 577 | 578 | h6 { 579 | font-size: 1.6rem; 580 | letter-spacing: 0; 581 | line-height: 1.4; 582 | } 583 | 584 | img { 585 | max-width: 100%; 586 | } 587 | 588 | .clearfix:after { 589 | clear: both; 590 | content: ' '; 591 | display: table; 592 | } 593 | 594 | .float-left { 595 | float: left; 596 | } 597 | 598 | .float-right { 599 | float: right; 600 | } 601 | -------------------------------------------------------------------------------- /example/readme.md: -------------------------------------------------------------------------------- 1 | ## example folder 2 | 3 | This is a independent project create by `vue-cli` for serving online example, output dir is `../docs`. 4 | -------------------------------------------------------------------------------- /example/src/App.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | -------------------------------------------------------------------------------- /example/src/common/const.js: -------------------------------------------------------------------------------- 1 | // config and constants. 2 | 3 | export const TAB_TYPE = { 4 | VIEW: 1, 5 | CODE: 2 6 | } 7 | 8 | export const DEFAULT_TAB = TAB_TYPE.VIEW 9 | -------------------------------------------------------------------------------- /example/src/common/gen-unique-id.js: -------------------------------------------------------------------------------- 1 | // gen a simple unique id. 2 | export default (prefix) => { 3 | return `${prefix}$${Math.random().toString(16).substr(9)}` 4 | } 5 | -------------------------------------------------------------------------------- /example/src/common/get-code-url.js: -------------------------------------------------------------------------------- 1 | export default () => { 2 | const hashValue = (location.hash || '').substr(2) 3 | if (hashValue) { 4 | return `https://github.com/tangbc/vue-virtual-scroll-list/tree/master/example/src/views/${hashValue}/Main.vue` 5 | } else { 6 | return `https://github.com/tangbc/vue-virtual-scroll-list` 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /example/src/common/sentences.js: -------------------------------------------------------------------------------- 1 | import { Random } from './mock' 2 | import { isMobile } from './ua' 3 | 4 | // retrun several random sentences 5 | export default function getSentences (min = 1, max = 6) { 6 | const sentences = sentenceArray[Random.pick([0, 1, 2])] 7 | const results = [] 8 | 9 | let counts = Random.integer(min, isMobile ? 3 : max) 10 | while (counts--) { 11 | results.push(Random.pick(sentences)) 12 | } 13 | return results.join('. ') + '.' 14 | } 15 | 16 | // Try Everything (From Zootopia) 17 | const sentence1 = [ 18 | "I messed up tonight I lost another fight", 19 | "I still mess up but I'll just start again", 20 | "I keep falling down I keep on hitting the ground", 21 | "I always get up now to see what's next", 22 | "Birds don't just fly they fall down and get up", 23 | "Nobody learns without getting it won", 24 | "I won't give up no I won't give in", 25 | "Till I reach the end and then I'll start again", 26 | "No I won't leave I wanna try everything", 27 | "I wanna try even though I could fail", 28 | "I won't give up no I won't give in", 29 | "Till I reach the end and then I'll start again", 30 | "No I won't leave I wanna try everything", 31 | "I wanna try even though I could fail", 32 | "Look at how far you've come you filled your heart with love", 33 | "Baby you've done enough that cut your breath", 34 | "Don't beat yourself up don't need to run so fast", 35 | "Sometimes we come last but we did our best", 36 | "I won't give up no I won't give in", 37 | "Till I reach the end and then I'll start again", 38 | "No I won't leave I wanna try everything", 39 | "I wanna try even though I could fail", 40 | "I won't give up no I won't give in", 41 | "Till I reach the end and then I'll start again", 42 | "No I won't leave I wanna try everything", 43 | "I wanna try even though I could fail", 44 | "I'll keep on making those new mistakes", 45 | "I'll keep on making them every day", 46 | "Those new mistakes" 47 | ] 48 | 49 | // Dream It Possible (From Delacey) 50 | const sentence2 = [ 51 | "I will run I will climb I will soar", 52 | "I'm undefeated", 53 | "Jumping out of my skin pull the chord", 54 | "Yeah I believe it", 55 | "The past is everything we were don't make us who we are", 56 | "So I'll dream until I make it real and all I see is stars", 57 | "It's not until you fall that you fly", 58 | "When your dreams come alive you're unstoppable", 59 | "Take a shot chase the sun find the beautiful", 60 | "We will glow in the dark turning dust to gold", 61 | "And we'll dream it possible", 62 | "And we'll dream it possible", 63 | "I will chase I will reach I will fly", 64 | "Until I'm breaking until I'm breaking", 65 | "Out of my cage like a bird in the night", 66 | "I know I'm changing I know I'm changing", 67 | "In into something big better than before", 68 | "And if it takes takes a thousand lives", 69 | "Then it's worth fighting for", 70 | "It's not until you fall that you fly", 71 | "When your dreams come alive you're unstoppable", 72 | "Take a shot chase the sun find the beautiful", 73 | "We will glow in the dark turning dust to gold", 74 | "And we'll dream it possible", 75 | "It possible", 76 | "From the bottom to the top", 77 | "We're sparking wild fire's", 78 | "Never quit and never stop", 79 | "The rest of our lives", 80 | "From the bottom to the top", 81 | "We're sparking wild fire's", 82 | "Never quit and never stop", 83 | "It's not until you fall that you fly", 84 | "When your dreams come alive you're unstoppable", 85 | "Take a shot chase the sun find the beautiful", 86 | "We will glow in the dark turning dust to gold", 87 | "And we'll dream it possible", 88 | "And we'll dream it possible" 89 | ] 90 | 91 | // The Climb (From Miley Cyrus) 92 | const sentence3 = [ 93 | "I can almost see it", 94 | "That dream I'm dreamin' but", 95 | "There's a voice inside my head saying", 96 | "You'll never reach it", 97 | "Every step I'm taking", 98 | "Every move I make feels", 99 | "Lost with no direction", 100 | "My faith is shakin", 101 | "But I I gotta keep tryin", 102 | "Gotta keep my head held high", 103 | "There's always gonna be another mountain", 104 | "I'm always gonna wanna make it move", 105 | "Always gonna be an uphill battle", 106 | "Sometimes I'm gonna have to lose", 107 | "Ain't about how fast I get there", 108 | "Ain't about what's waitin on the other side", 109 | "It's the climb", 110 | "The struggles I'm facing", 111 | "The chances I'm taking", 112 | "Sometimes might knock me down but", 113 | "No I'm not breaking", 114 | "I may not know it", 115 | "But these are the moments that", 116 | "I'm gonna remember most yeah", 117 | "Just gotta keep going", 118 | "And I I gotta be strong", 119 | "Just keep pushing on 'cause", 120 | "There's always gonna be another mountain", 121 | "I'm always gonna wanna make it move", 122 | "Always gonna be an uphill battle", 123 | "But Sometimes I'm gonna have to lose", 124 | "Ain't about how fast I get there", 125 | "Ain't about what's waitin on the other side", 126 | "It's the climb", 127 | "Yeah-yeah", 128 | "There's always gonna be another mountain", 129 | "I'm always gonna wanna make it move", 130 | "Always gonna be an uphill battle", 131 | "Sometimes you're gonna have to lose", 132 | "Ain't about how fast I get there", 133 | "Ain't about what's waitin on the other side", 134 | "It's the climb", 135 | "Yeah-yeah-yea", 136 | "Keep on moving", 137 | "Keep climbing", 138 | "Keep the faith", 139 | "Baby It's all about", 140 | "It's all about the climb", 141 | "Keep your faith", 142 | "Whoa O Whoa" 143 | ] 144 | 145 | const sentenceArray = [ 146 | sentence1, 147 | sentence2, 148 | sentence3 149 | ] 150 | -------------------------------------------------------------------------------- /example/src/common/ua.js: -------------------------------------------------------------------------------- 1 | const ua = navigator.userAgent 2 | const Android = !!ua.match(/Android/i) 3 | const iOS = !!ua.match(/iPhone|iPad|iPod/i) 4 | export const isMobile = Android || iOS 5 | -------------------------------------------------------------------------------- /example/src/common/user.js: -------------------------------------------------------------------------------- 1 | import { Random } from './mock' 2 | 3 | const getRandomAvatar = () => { 4 | return `https://avatars1.githubusercontent.com/u/${Random.integer(10000, 99999)}` 5 | } 6 | 7 | export default () => { 8 | return { 9 | name: Random.name(), 10 | avatar: getRandomAvatar() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /example/src/components/CodeHighLight.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 39 | 40 | 49 | -------------------------------------------------------------------------------- /example/src/components/Corner.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 27 | 28 | -------------------------------------------------------------------------------- /example/src/components/Introduction.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 17 | 18 | 29 | -------------------------------------------------------------------------------- /example/src/components/Tab.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 51 | 52 | 98 | -------------------------------------------------------------------------------- /example/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | 5 | import GithubButton from 'vue-github-button' 6 | import VirtualList from '../../src/index' 7 | import Introduction from './components/Introduction' 8 | import CodeHighLight from './components/CodeHighLight' 9 | import Corner from './components/Corner' 10 | import Tab from './components/Tab' 11 | 12 | Vue.component('virtual-list', VirtualList) 13 | Vue.component(Introduction.name, Introduction) 14 | Vue.component(CodeHighLight.name, CodeHighLight) 15 | Vue.component(Corner.name, Corner) 16 | Vue.component(Tab.name, Tab) 17 | Vue.component('github-button', GithubButton) 18 | 19 | Vue.config.devtools = false 20 | Vue.config.productionTip = false 21 | 22 | new Vue({ 23 | router, 24 | render: h => h(App) 25 | }).$mount('#app') 26 | -------------------------------------------------------------------------------- /example/src/mixins/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | methods: { 3 | dispatch(componentName, eventName, ...rest) { 4 | let parent = this.$parent || this.$root 5 | let name = parent.$options.name 6 | 7 | while (parent && (!name || name !== componentName)) { 8 | parent = parent.$parent 9 | if (parent) { 10 | name = parent.$options.name 11 | } 12 | } 13 | 14 | if (parent) { 15 | parent.$emit.apply(parent, [eventName].concat(rest)) 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /example/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import Index from '../views/home/Main.vue' 4 | 5 | Vue.use(VueRouter) 6 | 7 | const routes = [ 8 | { 9 | path: '/', 10 | name: 'home', 11 | component: Index 12 | }, 13 | { 14 | path: '/fixed-size', 15 | name: 'fixed-size', 16 | component: () => import(/* webpackChunkName: "fixed-size" */ '../views/fixed-size/Main.vue') 17 | }, 18 | { 19 | path: '/dynamic-size', 20 | name: 'dynamic-size', 21 | component: () => import(/* webpackChunkName: "dynamic-size" */ '../views/dynamic-size/Main.vue') 22 | }, 23 | { 24 | path: '/horizontal', 25 | name: 'horizontal', 26 | component: () => import(/* webpackChunkName: "horizontal" */ '../views/horizontal/Main.vue') 27 | }, 28 | { 29 | path: '/infinite-loading', 30 | name: 'infinite-loading', 31 | component: () => import(/* webpackChunkName: "infinite-loading" */ '../views/infinite-loading/Main.vue') 32 | }, 33 | { 34 | path: '/keep-state', 35 | name: 'keep-state', 36 | component: () => import(/* webpackChunkName: "keep-state" */ '../views/keep-state/Main.vue') 37 | }, 38 | { 39 | path: '/chat-room', 40 | name: 'chat-room', 41 | component: () => import(/* webpackChunkName: "chat-room" */ '../views/chat-room/Main.vue') 42 | }, 43 | { 44 | path: '/page-mode', 45 | name: 'page-mode', 46 | component: () => import(/* webpackChunkName: "page-mode" */ '../views/page-mode/Main.vue') 47 | } 48 | ] 49 | 50 | // just for development, if you want to run this project in your local 51 | // please copy a any example and rename it as dev in example/src/views folder 52 | if (process.env.NODE_ENV === 'development') { 53 | routes.push({ 54 | path: '/dev', 55 | name: 'dev', 56 | component: () => import(/* webpackChunkName: "dev" */ '../views/dev/Main.vue') 57 | }) 58 | } 59 | 60 | const router = new VueRouter({ 61 | routes 62 | }) 63 | 64 | export default router 65 | -------------------------------------------------------------------------------- /example/src/views/chat-room/Editor.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 108 | 109 | 156 | -------------------------------------------------------------------------------- /example/src/views/chat-room/Item.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 35 | 36 | -------------------------------------------------------------------------------- /example/src/views/chat-room/Main.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 180 | 181 | 294 | -------------------------------------------------------------------------------- /example/src/views/chat-room/Toolbar.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 39 | 40 | 56 | -------------------------------------------------------------------------------- /example/src/views/chat-room/util.js: -------------------------------------------------------------------------------- 1 | import { Random } from '../../common/mock' 2 | import getSentences from '../../common/sentences' 3 | import getUser from '../../common/user' 4 | 5 | let sidCounter = 0 6 | const maxCounts = 500 7 | 8 | export function genSid () { 9 | return `sid-${sidCounter++}` 10 | } 11 | 12 | export function genBody () { 13 | return { 14 | user: {}, 15 | sid: '', 16 | content: '', 17 | images: [], 18 | isCreator: false 19 | } 20 | } 21 | 22 | export function getMessages (numbers, delay) { 23 | return new Promise((resolve) => { 24 | if (sidCounter >= maxCounts) { 25 | resolve([]) 26 | return 27 | } 28 | 29 | setTimeout(() => { 30 | const messages = [] 31 | while (numbers--) { 32 | let body = genBody() 33 | body.user = getUser() 34 | body.content = getSentences() 35 | body.sid = genSid() 36 | messages.push(body) 37 | } 38 | 39 | resolve(messages) 40 | }, delay ? Random.pick([300, 500, 800]) : 0) 41 | }) 42 | } 43 | 44 | export function getSids (messages) { 45 | return messages.map((message) => message.sid) 46 | } 47 | 48 | export const LOAD_TYPES = { 49 | EMPTY: 'EMPTY', 50 | PAGES: 'PAGES', 51 | FEW: 'FEW' 52 | } 53 | 54 | export function setLoadType (type) { 55 | try { 56 | sessionStorage.setItem('LOAD_TYPES', type) 57 | } catch (e) { 58 | console.error(e) 59 | } 60 | } 61 | 62 | export function getLoadType () { 63 | try { 64 | return sessionStorage.getItem('LOAD_TYPES') || LOAD_TYPES.PAGES 65 | } catch (e) { 66 | console.error(e) 67 | return LOAD_TYPES.EMPTY 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /example/src/views/dynamic-size/Code.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 50 | 51 | -------------------------------------------------------------------------------- /example/src/views/dynamic-size/Item.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 26 | 27 | -------------------------------------------------------------------------------- /example/src/views/dynamic-size/Main.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 75 | 76 | 94 | -------------------------------------------------------------------------------- /example/src/views/fixed-size/Code.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 50 | 51 | -------------------------------------------------------------------------------- /example/src/views/fixed-size/Item.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 23 | 24 | -------------------------------------------------------------------------------- /example/src/views/fixed-size/Main.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 69 | 70 | 89 | -------------------------------------------------------------------------------- /example/src/views/home/Main.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 54 | 55 | 87 | -------------------------------------------------------------------------------- /example/src/views/horizontal/Code.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 33 | 34 | -------------------------------------------------------------------------------- /example/src/views/horizontal/Item.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 23 | 24 | -------------------------------------------------------------------------------- /example/src/views/horizontal/Main.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 71 | 72 | 91 | -------------------------------------------------------------------------------- /example/src/views/infinite-loading/Code.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 48 | 49 | -------------------------------------------------------------------------------- /example/src/views/infinite-loading/Item.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 26 | 27 | -------------------------------------------------------------------------------- /example/src/views/infinite-loading/Main.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 102 | 103 | 181 | -------------------------------------------------------------------------------- /example/src/views/keep-state/Code.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 52 | 53 | 58 | 59 | -------------------------------------------------------------------------------- /example/src/views/keep-state/Item.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 37 | 38 | -------------------------------------------------------------------------------- /example/src/views/keep-state/Main.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 91 | 92 | 115 | -------------------------------------------------------------------------------- /example/src/views/page-mode/Item.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 26 | 27 | 41 | -------------------------------------------------------------------------------- /example/src/views/page-mode/Main.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 70 | 71 | 91 | -------------------------------------------------------------------------------- /example/vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | // https://cli.vuejs.org/config/#global-cli-config 4 | module.exports = { 5 | publicPath: './', 6 | 7 | outputDir: path.resolve(__dirname, '../docs'), 8 | 9 | productionSourceMap: false 10 | } 11 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transformIgnorePatterns: [ 3 | '/node_modules' 4 | ], 5 | collectCoverage: true, 6 | coverageDirectory: '/coverage', 7 | coverageReporters: [ 8 | 'lcov', 9 | 'html', 10 | 'text-summary', 11 | ], 12 | collectCoverageFrom: [ 13 | '/src/*.js' 14 | ], 15 | moduleFileExtensions: [ 16 | 'js', 17 | 'vue' 18 | ], 19 | transform: { 20 | '.*\\.(vue)$': 'vue-jest', 21 | '^.+\\.js$': '/node_modules/babel-jest' 22 | }, 23 | // testRegex: 'scroll.test.js?$' 24 | } 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-virtual-scroll-list", 3 | "version": "2.3.5", 4 | "description": "A vue component support big amount data list with high scroll performance.", 5 | "main": "dist/index.js", 6 | "files": [ 7 | "src", 8 | "dist" 9 | ], 10 | "scripts": { 11 | "lint": "eslint --fix src --ext .js", 12 | "dev:docs": "cd example && npm run dev", 13 | "build:docs": "cd example && npm run build && cp ../dist/index.js ../docs", 14 | "build": "npm run lint && rollup --config ./scripts/rollup.production.js", 15 | "dev": "rollup --config ./scripts/rollup.development.js --watch", 16 | "test": "rm -rf ./coverage && jest" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/tangbc/vue-virtual-scroll-list.git" 21 | }, 22 | "keywords": [ 23 | "vue", 24 | "big-data", 25 | "big-list", 26 | "scroll-list", 27 | "virtual-list" 28 | ], 29 | "author": "tangbc", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/tangbc/vue-virtual-scroll-list/issues" 33 | }, 34 | "homepage": "https://github.com/tangbc/vue-virtual-scroll-list#readme", 35 | "dependencies": {}, 36 | "devDependencies": { 37 | "@babel/core": "^7.8.7", 38 | "@babel/preset-env": "^7.8.7", 39 | "@vue/test-utils": "^1.0.0-beta.33", 40 | "babel-core": "^7.0.0-bridge.0", 41 | "babel-jest": "^25.5.1", 42 | "eslint": "^6.8.0", 43 | "eslint-config-standard": "^14.1.1", 44 | "eslint-loader": "^4.0.0", 45 | "eslint-plugin-import": "^2.20.2", 46 | "eslint-plugin-node": "^11.1.0", 47 | "eslint-plugin-promise": "^4.2.1", 48 | "eslint-plugin-standard": "^4.0.1", 49 | "eslint-plugin-vue": "^6.2.2", 50 | "jest": "^25.5.1", 51 | "rollup": "^2.1.0", 52 | "rollup-plugin-babel": "^4.4.0", 53 | "vue": "^2.6.11", 54 | "vue-jest": "^3.0.5", 55 | "vue-template-compiler": "^2.6.11" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /scripts/rollup.banner.js: -------------------------------------------------------------------------------- 1 | const packageJson = require('../package.json') 2 | const version = packageJson.version 3 | const homepage = packageJson.homepage 4 | 5 | export default ` 6 | /*! 7 | * vue-virtual-scroll-list v${version} 8 | * open source under the MIT license 9 | * ${homepage} 10 | */ 11 | ` 12 | -------------------------------------------------------------------------------- /scripts/rollup.development.js: -------------------------------------------------------------------------------- 1 | import ProductionConfig from './rollup.production' 2 | 3 | // development mode just rewrite from production config. 4 | export default Object.assign({}, ProductionConfig, { 5 | output: { 6 | ...ProductionConfig.output, 7 | file: './dev/index.js', 8 | sourcemap: true, 9 | banner: '/* eslint-disable */' // disable eslint when bundle with docs. 10 | } 11 | }) 12 | -------------------------------------------------------------------------------- /scripts/rollup.production.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel' 2 | import bannerString from './rollup.banner' 3 | 4 | export default { 5 | external: ['vue'], 6 | input: './src/index.js', 7 | output: { 8 | format: 'umd', 9 | file: './dist/index.js', 10 | name: 'VirtualList', 11 | sourcemap: false, 12 | globals: { 13 | vue: 'Vue', 14 | }, 15 | banner: bannerString.replace(/\n/, '') 16 | }, 17 | plugins: [ 18 | babel() 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * virtual list default component 3 | */ 4 | 5 | import Vue from 'vue' 6 | import Virtual from './virtual' 7 | import { Item, Slot } from './item' 8 | import { VirtualProps } from './props' 9 | 10 | const EVENT_TYPE = { 11 | ITEM: 'item_resize', 12 | SLOT: 'slot_resize' 13 | } 14 | const SLOT_TYPE = { 15 | HEADER: 'thead', // string value also use for aria role attribute 16 | FOOTER: 'tfoot' 17 | } 18 | 19 | const VirtualList = Vue.component('virtual-list', { 20 | props: VirtualProps, 21 | 22 | data () { 23 | return { 24 | range: null 25 | } 26 | }, 27 | 28 | watch: { 29 | 'dataSources.length' () { 30 | this.virtual.updateParam('uniqueIds', this.getUniqueIdFromDataSources()) 31 | this.virtual.handleDataSourcesChange() 32 | }, 33 | 34 | keeps (newValue) { 35 | this.virtual.updateParam('keeps', newValue) 36 | this.virtual.handleSlotSizeChange() 37 | }, 38 | 39 | start (newValue) { 40 | this.scrollToIndex(newValue) 41 | }, 42 | 43 | offset (newValue) { 44 | this.scrollToOffset(newValue) 45 | } 46 | }, 47 | 48 | created () { 49 | this.isHorizontal = this.direction === 'horizontal' 50 | this.directionKey = this.isHorizontal ? 'scrollLeft' : 'scrollTop' 51 | 52 | this.installVirtual() 53 | 54 | // listen item size change 55 | this.$on(EVENT_TYPE.ITEM, this.onItemResized) 56 | 57 | // listen slot size change 58 | if (this.$slots.header || this.$slots.footer) { 59 | this.$on(EVENT_TYPE.SLOT, this.onSlotResized) 60 | } 61 | }, 62 | 63 | activated () { 64 | // set back offset when awake from keep-alive 65 | this.scrollToOffset(this.virtual.offset) 66 | 67 | if (this.pageMode) { 68 | document.addEventListener('scroll', this.onScroll, { 69 | passive: false 70 | }) 71 | } 72 | }, 73 | 74 | deactivated () { 75 | if (this.pageMode) { 76 | document.removeEventListener('scroll', this.onScroll) 77 | } 78 | }, 79 | 80 | mounted () { 81 | // set position 82 | if (this.start) { 83 | this.scrollToIndex(this.start) 84 | } else if (this.offset) { 85 | this.scrollToOffset(this.offset) 86 | } 87 | 88 | // in page mode we bind scroll event to document 89 | if (this.pageMode) { 90 | this.updatePageModeFront() 91 | 92 | document.addEventListener('scroll', this.onScroll, { 93 | passive: false 94 | }) 95 | } 96 | }, 97 | 98 | beforeDestroy () { 99 | this.virtual.destroy() 100 | if (this.pageMode) { 101 | document.removeEventListener('scroll', this.onScroll) 102 | } 103 | }, 104 | 105 | methods: { 106 | // get item size by id 107 | getSize (id) { 108 | return this.virtual.sizes.get(id) 109 | }, 110 | 111 | // get the total number of stored (rendered) items 112 | getSizes () { 113 | return this.virtual.sizes.size 114 | }, 115 | 116 | // return current scroll offset 117 | getOffset () { 118 | if (this.pageMode) { 119 | return document.documentElement[this.directionKey] || document.body[this.directionKey] 120 | } else { 121 | const { root } = this.$refs 122 | return root ? Math.ceil(root[this.directionKey]) : 0 123 | } 124 | }, 125 | 126 | // return client viewport size 127 | getClientSize () { 128 | const key = this.isHorizontal ? 'clientWidth' : 'clientHeight' 129 | if (this.pageMode) { 130 | return document.documentElement[key] || document.body[key] 131 | } else { 132 | const { root } = this.$refs 133 | return root ? Math.ceil(root[key]) : 0 134 | } 135 | }, 136 | 137 | // return all scroll size 138 | getScrollSize () { 139 | const key = this.isHorizontal ? 'scrollWidth' : 'scrollHeight' 140 | if (this.pageMode) { 141 | return document.documentElement[key] || document.body[key] 142 | } else { 143 | const { root } = this.$refs 144 | return root ? Math.ceil(root[key]) : 0 145 | } 146 | }, 147 | 148 | // set current scroll position to a expectant offset 149 | scrollToOffset (offset) { 150 | if (this.pageMode) { 151 | document.body[this.directionKey] = offset 152 | document.documentElement[this.directionKey] = offset 153 | } else { 154 | const { root } = this.$refs 155 | if (root) { 156 | root[this.directionKey] = offset 157 | } 158 | } 159 | }, 160 | 161 | // set current scroll position to a expectant index 162 | scrollToIndex (index) { 163 | // scroll to bottom 164 | if (index >= this.dataSources.length - 1) { 165 | this.scrollToBottom() 166 | } else { 167 | const offset = this.virtual.getOffset(index) 168 | this.scrollToOffset(offset) 169 | } 170 | }, 171 | 172 | // set current scroll position to bottom 173 | scrollToBottom () { 174 | const { shepherd } = this.$refs 175 | if (shepherd) { 176 | const offset = shepherd[this.isHorizontal ? 'offsetLeft' : 'offsetTop'] 177 | this.scrollToOffset(offset) 178 | 179 | // check if it's really scrolled to the bottom 180 | // maybe list doesn't render and calculate to last range 181 | // so we need retry in next event loop until it really at bottom 182 | setTimeout(() => { 183 | if (this.getOffset() + this.getClientSize() + 1 < this.getScrollSize()) { 184 | this.scrollToBottom() 185 | } 186 | }, 3) 187 | } 188 | }, 189 | 190 | // when using page mode we need update slot header size manually 191 | // taking root offset relative to the browser as slot header size 192 | updatePageModeFront () { 193 | const { root } = this.$refs 194 | if (root) { 195 | const rect = root.getBoundingClientRect() 196 | const { defaultView } = root.ownerDocument 197 | const offsetFront = this.isHorizontal ? (rect.left + defaultView.pageXOffset) : (rect.top + defaultView.pageYOffset) 198 | this.virtual.updateParam('slotHeaderSize', offsetFront) 199 | } 200 | }, 201 | 202 | // reset all state back to initial 203 | reset () { 204 | this.virtual.destroy() 205 | this.scrollToOffset(0) 206 | this.installVirtual() 207 | }, 208 | 209 | // ----------- public method end ----------- 210 | 211 | installVirtual () { 212 | this.virtual = new Virtual({ 213 | slotHeaderSize: 0, 214 | slotFooterSize: 0, 215 | keeps: this.keeps, 216 | estimateSize: this.estimateSize, 217 | buffer: Math.round(this.keeps / 3), // recommend for a third of keeps 218 | uniqueIds: this.getUniqueIdFromDataSources() 219 | }, this.onRangeChanged) 220 | 221 | // sync initial range 222 | this.range = this.virtual.getRange() 223 | }, 224 | 225 | getUniqueIdFromDataSources () { 226 | const { dataKey } = this 227 | return this.dataSources.map((dataSource) => typeof dataKey === 'function' ? dataKey(dataSource) : dataSource[dataKey]) 228 | }, 229 | 230 | // event called when each item mounted or size changed 231 | onItemResized (id, size) { 232 | this.virtual.saveSize(id, size) 233 | this.$emit('resized', id, size) 234 | }, 235 | 236 | // event called when slot mounted or size changed 237 | onSlotResized (type, size, hasInit) { 238 | if (type === SLOT_TYPE.HEADER) { 239 | this.virtual.updateParam('slotHeaderSize', size) 240 | } else if (type === SLOT_TYPE.FOOTER) { 241 | this.virtual.updateParam('slotFooterSize', size) 242 | } 243 | 244 | if (hasInit) { 245 | this.virtual.handleSlotSizeChange() 246 | } 247 | }, 248 | 249 | // here is the rerendering entry 250 | onRangeChanged (range) { 251 | this.range = range 252 | }, 253 | 254 | onScroll (evt) { 255 | const offset = this.getOffset() 256 | const clientSize = this.getClientSize() 257 | const scrollSize = this.getScrollSize() 258 | 259 | // iOS scroll-spring-back behavior will make direction mistake 260 | if (offset < 0 || (offset + clientSize > scrollSize + 1) || !scrollSize) { 261 | return 262 | } 263 | 264 | this.virtual.handleScroll(offset) 265 | this.emitEvent(offset, clientSize, scrollSize, evt) 266 | }, 267 | 268 | // emit event in special position 269 | emitEvent (offset, clientSize, scrollSize, evt) { 270 | this.$emit('scroll', evt, this.virtual.getRange()) 271 | 272 | if (this.virtual.isFront() && !!this.dataSources.length && (offset - this.topThreshold <= 0)) { 273 | this.$emit('totop') 274 | } else if (this.virtual.isBehind() && (offset + clientSize + this.bottomThreshold >= scrollSize)) { 275 | this.$emit('tobottom') 276 | } 277 | }, 278 | 279 | // get the real render slots based on range data 280 | // in-place patch strategy will try to reuse components as possible 281 | // so those components that are reused will not trigger lifecycle mounted 282 | getRenderSlots (h) { 283 | const slots = [] 284 | const { start, end } = this.range 285 | const { dataSources, dataKey, itemClass, itemTag, itemStyle, isHorizontal, extraProps, dataComponent, itemScopedSlots } = this 286 | const slotComponent = this.$scopedSlots && this.$scopedSlots.item 287 | for (let index = start; index <= end; index++) { 288 | const dataSource = dataSources[index] 289 | if (dataSource) { 290 | const uniqueKey = typeof dataKey === 'function' ? dataKey(dataSource) : dataSource[dataKey] 291 | if (typeof uniqueKey === 'string' || typeof uniqueKey === 'number') { 292 | slots.push(h(Item, { 293 | props: { 294 | index, 295 | tag: itemTag, 296 | event: EVENT_TYPE.ITEM, 297 | horizontal: isHorizontal, 298 | uniqueKey: uniqueKey, 299 | source: dataSource, 300 | extraProps: extraProps, 301 | component: dataComponent, 302 | slotComponent: slotComponent, 303 | scopedSlots: itemScopedSlots 304 | }, 305 | style: itemStyle, 306 | class: `${itemClass}${this.itemClassAdd ? ' ' + this.itemClassAdd(index) : ''}` 307 | })) 308 | } else { 309 | console.warn(`Cannot get the data-key '${dataKey}' from data-sources.`) 310 | } 311 | } else { 312 | console.warn(`Cannot get the index '${index}' from data-sources.`) 313 | } 314 | } 315 | return slots 316 | } 317 | }, 318 | 319 | // render function, a closer-to-the-compiler alternative to templates 320 | // https://vuejs.org/v2/guide/render-function.html#The-Data-Object-In-Depth 321 | render (h) { 322 | const { header, footer } = this.$slots 323 | const { padFront, padBehind } = this.range 324 | const { isHorizontal, pageMode, rootTag, wrapTag, wrapClass, wrapStyle, headerTag, headerClass, headerStyle, footerTag, footerClass, footerStyle } = this 325 | const paddingStyle = { padding: isHorizontal ? `0px ${padBehind}px 0px ${padFront}px` : `${padFront}px 0px ${padBehind}px` } 326 | const wrapperStyle = wrapStyle ? Object.assign({}, wrapStyle, paddingStyle) : paddingStyle 327 | 328 | return h(rootTag, { 329 | ref: 'root', 330 | on: { 331 | '&scroll': !pageMode && this.onScroll 332 | } 333 | }, [ 334 | // header slot 335 | header ? h(Slot, { 336 | class: headerClass, 337 | style: headerStyle, 338 | props: { 339 | tag: headerTag, 340 | event: EVENT_TYPE.SLOT, 341 | uniqueKey: SLOT_TYPE.HEADER 342 | } 343 | }, header) : null, 344 | 345 | // main list 346 | h(wrapTag, { 347 | class: wrapClass, 348 | attrs: { 349 | role: 'group' 350 | }, 351 | style: wrapperStyle 352 | }, this.getRenderSlots(h)), 353 | 354 | // footer slot 355 | footer ? h(Slot, { 356 | class: footerClass, 357 | style: footerStyle, 358 | props: { 359 | tag: footerTag, 360 | event: EVENT_TYPE.SLOT, 361 | uniqueKey: SLOT_TYPE.FOOTER 362 | } 363 | }, footer) : null, 364 | 365 | // an empty element use to scroll to bottom 366 | h('div', { 367 | ref: 'shepherd', 368 | style: { 369 | width: isHorizontal ? '0px' : '100%', 370 | height: isHorizontal ? '100%' : '0px' 371 | } 372 | }) 373 | ]) 374 | } 375 | }) 376 | 377 | export default VirtualList 378 | -------------------------------------------------------------------------------- /src/item.js: -------------------------------------------------------------------------------- 1 | /** 2 | * item and slot component both use similar wrapper 3 | * we need to know their size change at any time 4 | */ 5 | 6 | import Vue from 'vue' 7 | import { ItemProps, SlotProps } from './props' 8 | 9 | const Wrapper = { 10 | created () { 11 | this.shapeKey = this.horizontal ? 'offsetWidth' : 'offsetHeight' 12 | }, 13 | 14 | mounted () { 15 | if (typeof ResizeObserver !== 'undefined') { 16 | this.resizeObserver = new ResizeObserver(() => { 17 | this.dispatchSizeChange() 18 | }) 19 | this.resizeObserver.observe(this.$el) 20 | } 21 | }, 22 | 23 | // since componet will be reused, so disptach when updated 24 | updated () { 25 | // this.dispatchSizeChange() 26 | this.resizeObserver.observe(this.$el) 27 | }, 28 | 29 | beforeDestroy () { 30 | if (this.resizeObserver) { 31 | this.resizeObserver.disconnect() 32 | this.resizeObserver = null 33 | } 34 | }, 35 | 36 | methods: { 37 | getCurrentSize () { 38 | return this.$el ? this.$el[this.shapeKey] : 0 39 | }, 40 | 41 | // tell parent current size identify by unqiue key 42 | dispatchSizeChange () { 43 | this.$parent.$emit(this.event, this.uniqueKey, this.getCurrentSize(), this.hasInitial) 44 | } 45 | } 46 | } 47 | 48 | // wrapping for item 49 | export const Item = Vue.component('virtual-list-item', { 50 | mixins: [Wrapper], 51 | 52 | props: ItemProps, 53 | 54 | render (h) { 55 | const { tag, component, extraProps = {}, index, source, scopedSlots = {}, uniqueKey, slotComponent } = this 56 | const props = { 57 | ...extraProps, 58 | source, 59 | index 60 | } 61 | 62 | return h(tag, { 63 | key: uniqueKey, 64 | attrs: { 65 | role: 'listitem' 66 | } 67 | }, [slotComponent ? slotComponent({ item: source, index: index, scope: props }) : h(component, { 68 | props, 69 | scopedSlots: scopedSlots 70 | })]) 71 | } 72 | }) 73 | 74 | // wrapping for slot 75 | export const Slot = Vue.component('virtual-list-slot', { 76 | mixins: [Wrapper], 77 | 78 | props: SlotProps, 79 | 80 | render (h) { 81 | const { tag, uniqueKey } = this 82 | 83 | return h(tag, { 84 | key: uniqueKey, 85 | attrs: { 86 | role: uniqueKey 87 | } 88 | }, this.$slots.default) 89 | } 90 | }) 91 | -------------------------------------------------------------------------------- /src/props.js: -------------------------------------------------------------------------------- 1 | /** 2 | * props declaration for default, item and slot component 3 | */ 4 | 5 | export const VirtualProps = { 6 | dataKey: { 7 | type: [String, Function], 8 | required: true 9 | }, 10 | dataSources: { 11 | type: Array, 12 | required: true 13 | }, 14 | dataComponent: { 15 | type: [Object, Function], 16 | required: true 17 | }, 18 | 19 | keeps: { 20 | type: Number, 21 | default: 30 22 | }, 23 | extraProps: { 24 | type: Object 25 | }, 26 | estimateSize: { 27 | type: Number, 28 | default: 50 29 | }, 30 | 31 | direction: { 32 | type: String, 33 | default: 'vertical' // the other value is horizontal 34 | }, 35 | start: { 36 | type: Number, 37 | default: 0 38 | }, 39 | offset: { 40 | type: Number, 41 | default: 0 42 | }, 43 | topThreshold: { 44 | type: Number, 45 | default: 0 46 | }, 47 | bottomThreshold: { 48 | type: Number, 49 | default: 0 50 | }, 51 | pageMode: { 52 | type: Boolean, 53 | default: false 54 | }, 55 | rootTag: { 56 | type: String, 57 | default: 'div' 58 | }, 59 | wrapTag: { 60 | type: String, 61 | default: 'div' 62 | }, 63 | wrapClass: { 64 | type: String, 65 | default: '' 66 | }, 67 | wrapStyle: { 68 | type: Object 69 | }, 70 | itemTag: { 71 | type: String, 72 | default: 'div' 73 | }, 74 | itemClass: { 75 | type: String, 76 | default: '' 77 | }, 78 | itemClassAdd: { 79 | type: Function 80 | }, 81 | itemStyle: { 82 | type: Object 83 | }, 84 | headerTag: { 85 | type: String, 86 | default: 'div' 87 | }, 88 | headerClass: { 89 | type: String, 90 | default: '' 91 | }, 92 | headerStyle: { 93 | type: Object 94 | }, 95 | footerTag: { 96 | type: String, 97 | default: 'div' 98 | }, 99 | footerClass: { 100 | type: String, 101 | default: '' 102 | }, 103 | footerStyle: { 104 | type: Object 105 | }, 106 | itemScopedSlots: { 107 | type: Object 108 | } 109 | } 110 | 111 | export const ItemProps = { 112 | index: { 113 | type: Number 114 | }, 115 | event: { 116 | type: String 117 | }, 118 | tag: { 119 | type: String 120 | }, 121 | horizontal: { 122 | type: Boolean 123 | }, 124 | source: { 125 | type: Object 126 | }, 127 | component: { 128 | type: [Object, Function] 129 | }, 130 | slotComponent: { 131 | type: Function 132 | }, 133 | uniqueKey: { 134 | type: [String, Number] 135 | }, 136 | extraProps: { 137 | type: Object 138 | }, 139 | scopedSlots: { 140 | type: Object 141 | } 142 | } 143 | 144 | export const SlotProps = { 145 | event: { 146 | type: String 147 | }, 148 | uniqueKey: { 149 | type: String 150 | }, 151 | tag: { 152 | type: String 153 | }, 154 | horizontal: { 155 | type: Boolean 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/virtual.js: -------------------------------------------------------------------------------- 1 | /** 2 | * virtual list core calculating center 3 | */ 4 | 5 | const DIRECTION_TYPE = { 6 | FRONT: 'FRONT', // scroll up or left 7 | BEHIND: 'BEHIND' // scroll down or right 8 | } 9 | const CALC_TYPE = { 10 | INIT: 'INIT', 11 | FIXED: 'FIXED', 12 | DYNAMIC: 'DYNAMIC' 13 | } 14 | const LEADING_BUFFER = 0 15 | 16 | export default class Virtual { 17 | constructor (param, callUpdate) { 18 | this.init(param, callUpdate) 19 | } 20 | 21 | init (param, callUpdate) { 22 | // param data 23 | this.param = param 24 | this.callUpdate = callUpdate 25 | 26 | // size data 27 | this.sizes = new Map() 28 | this.firstRangeTotalSize = 0 29 | this.firstRangeAverageSize = 0 30 | this.fixedSizeValue = 0 31 | this.calcType = CALC_TYPE.INIT 32 | 33 | // scroll data 34 | this.offset = 0 35 | this.direction = '' 36 | 37 | // range data 38 | this.range = Object.create(null) 39 | if (param) { 40 | this.checkRange(0, param.keeps - 1) 41 | } 42 | 43 | // benchmark test data 44 | // this.__bsearchCalls = 0 45 | // this.__getIndexOffsetCalls = 0 46 | } 47 | 48 | destroy () { 49 | this.init(null, null) 50 | } 51 | 52 | // return current render range 53 | getRange () { 54 | const range = Object.create(null) 55 | range.start = this.range.start 56 | range.end = this.range.end 57 | range.padFront = this.range.padFront 58 | range.padBehind = this.range.padBehind 59 | return range 60 | } 61 | 62 | isBehind () { 63 | return this.direction === DIRECTION_TYPE.BEHIND 64 | } 65 | 66 | isFront () { 67 | return this.direction === DIRECTION_TYPE.FRONT 68 | } 69 | 70 | // return start index offset 71 | getOffset (start) { 72 | return (start < 1 ? 0 : this.getIndexOffset(start)) + this.param.slotHeaderSize 73 | } 74 | 75 | updateParam (key, value) { 76 | if (this.param && (key in this.param)) { 77 | // if uniqueIds change, find out deleted id and remove from size map 78 | if (key === 'uniqueIds') { 79 | this.sizes.forEach((v, key) => { 80 | if (!value.includes(key)) { 81 | this.sizes.delete(key) 82 | } 83 | }) 84 | } 85 | this.param[key] = value 86 | } 87 | } 88 | 89 | // save each size map by id 90 | saveSize (id, size) { 91 | this.sizes.set(id, size) 92 | 93 | // we assume size type is fixed at the beginning and remember first size value 94 | // if there is no size value different from this at next comming saving 95 | // we think it's a fixed size list, otherwise is dynamic size list 96 | if (this.calcType === CALC_TYPE.INIT) { 97 | this.fixedSizeValue = size 98 | this.calcType = CALC_TYPE.FIXED 99 | } else if (this.calcType === CALC_TYPE.FIXED && this.fixedSizeValue !== size) { 100 | this.calcType = CALC_TYPE.DYNAMIC 101 | // it's no use at all 102 | delete this.fixedSizeValue 103 | } 104 | 105 | // calculate the average size only in the first range 106 | if (this.calcType !== CALC_TYPE.FIXED && typeof this.firstRangeTotalSize !== 'undefined') { 107 | if (this.sizes.size < Math.min(this.param.keeps, this.param.uniqueIds.length)) { 108 | this.firstRangeTotalSize = [...this.sizes.values()].reduce((acc, val) => acc + val, 0) 109 | this.firstRangeAverageSize = Math.round(this.firstRangeTotalSize / this.sizes.size) 110 | } else { 111 | // it's done using 112 | delete this.firstRangeTotalSize 113 | } 114 | } 115 | } 116 | 117 | // in some special situation (e.g. length change) we need to update in a row 118 | // try goiong to render next range by a leading buffer according to current direction 119 | handleDataSourcesChange () { 120 | let start = this.range.start 121 | 122 | if (this.isFront()) { 123 | start = start - LEADING_BUFFER 124 | } else if (this.isBehind()) { 125 | start = start + LEADING_BUFFER 126 | } 127 | 128 | start = Math.max(start, 0) 129 | 130 | this.updateRange(this.range.start, this.getEndByStart(start)) 131 | } 132 | 133 | // when slot size change, we also need force update 134 | handleSlotSizeChange () { 135 | this.handleDataSourcesChange() 136 | } 137 | 138 | // calculating range on scroll 139 | handleScroll (offset) { 140 | this.direction = offset < this.offset || offset === 0 ? DIRECTION_TYPE.FRONT : DIRECTION_TYPE.BEHIND 141 | this.offset = offset 142 | 143 | if (!this.param) { 144 | return 145 | } 146 | 147 | if (this.direction === DIRECTION_TYPE.FRONT) { 148 | this.handleFront() 149 | } else if (this.direction === DIRECTION_TYPE.BEHIND) { 150 | this.handleBehind() 151 | } 152 | } 153 | 154 | // ----------- public method end ----------- 155 | 156 | handleFront () { 157 | const overs = this.getScrollOvers() 158 | // should not change range if start doesn't exceed overs 159 | if (overs > this.range.start) { 160 | return 161 | } 162 | 163 | // move up start by a buffer length, and make sure its safety 164 | const start = Math.max(overs - this.param.buffer, 0) 165 | this.checkRange(start, this.getEndByStart(start)) 166 | } 167 | 168 | handleBehind () { 169 | const overs = this.getScrollOvers() 170 | // range should not change if scroll overs within buffer 171 | if (overs < this.range.start + this.param.buffer) { 172 | return 173 | } 174 | 175 | this.checkRange(overs, this.getEndByStart(overs)) 176 | } 177 | 178 | // return the pass overs according to current scroll offset 179 | getScrollOvers () { 180 | // if slot header exist, we need subtract its size 181 | const offset = this.offset - this.param.slotHeaderSize 182 | if (offset <= 0) { 183 | return 0 184 | } 185 | 186 | // if is fixed type, that can be easily 187 | if (this.isFixedType()) { 188 | return Math.floor(offset / this.fixedSizeValue) 189 | } 190 | 191 | let low = 0 192 | let middle = 0 193 | let middleOffset = 0 194 | let high = this.param.uniqueIds.length 195 | 196 | while (low <= high) { 197 | // this.__bsearchCalls++ 198 | middle = low + Math.floor((high - low) / 2) 199 | middleOffset = this.getIndexOffset(middle) 200 | 201 | if (middleOffset === offset) { 202 | return middle 203 | } else if (middleOffset < offset) { 204 | low = middle + 1 205 | } else if (middleOffset > offset) { 206 | high = middle - 1 207 | } 208 | } 209 | 210 | return low > 0 ? --low : 0 211 | } 212 | 213 | // return a scroll offset from given index, can efficiency be improved more here? 214 | // although the call frequency is very high, its only a superposition of numbers 215 | getIndexOffset (givenIndex) { 216 | if (!givenIndex) { 217 | return 0 218 | } 219 | 220 | let offset = 0 221 | let indexSize = 0 222 | for (let index = 0; index < givenIndex; index++) { 223 | // this.__getIndexOffsetCalls++ 224 | indexSize = this.sizes.get(this.param.uniqueIds[index]) 225 | offset = offset + (typeof indexSize === 'number' ? indexSize : this.getEstimateSize()) 226 | } 227 | 228 | return offset 229 | } 230 | 231 | // is fixed size type 232 | isFixedType () { 233 | return this.calcType === CALC_TYPE.FIXED 234 | } 235 | 236 | // return the real last index 237 | getLastIndex () { 238 | return this.param.uniqueIds.length - 1 239 | } 240 | 241 | // in some conditions range is broke, we need correct it 242 | // and then decide whether need update to next range 243 | checkRange (start, end) { 244 | const keeps = this.param.keeps 245 | const total = this.param.uniqueIds.length 246 | 247 | // datas less than keeps, render all 248 | if (total <= keeps) { 249 | start = 0 250 | end = this.getLastIndex() 251 | } else if (end - start < keeps - 1) { 252 | // if range length is less than keeps, corrent it base on end 253 | start = end - keeps + 1 254 | } 255 | 256 | if (this.range.start !== start) { 257 | this.updateRange(start, end) 258 | } 259 | } 260 | 261 | // setting to a new range and rerender 262 | updateRange (start, end) { 263 | this.range.start = start 264 | this.range.end = end 265 | this.range.padFront = this.getPadFront() 266 | this.range.padBehind = this.getPadBehind() 267 | this.callUpdate(this.getRange()) 268 | } 269 | 270 | // return end base on start 271 | getEndByStart (start) { 272 | const theoryEnd = start + this.param.keeps - 1 273 | const truelyEnd = Math.min(theoryEnd, this.getLastIndex()) 274 | return truelyEnd 275 | } 276 | 277 | // return total front offset 278 | getPadFront () { 279 | if (this.isFixedType()) { 280 | return this.fixedSizeValue * this.range.start 281 | } else { 282 | return this.getIndexOffset(this.range.start) 283 | } 284 | } 285 | 286 | // return total behind offset 287 | getPadBehind () { 288 | const end = this.range.end 289 | const lastIndex = this.getLastIndex() 290 | 291 | if (this.isFixedType()) { 292 | return (lastIndex - end) * this.fixedSizeValue 293 | } 294 | 295 | return (lastIndex - end) * this.getEstimateSize() 296 | } 297 | 298 | // get the item estimate size 299 | getEstimateSize () { 300 | return this.isFixedType() ? this.fixedSizeValue : (this.firstRangeAverageSize || this.param.estimateSize) 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /test/base.test.js: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | import VirtualList from '../src/index' 3 | import { VirtualProps } from '../src/props' 4 | import Item from './item.vue' 5 | import { getDatas } from './util' 6 | 7 | describe('base', () => { 8 | const Instance = mount({ 9 | name: 'test', 10 | components: { 11 | 'virtual-list': VirtualList 12 | }, 13 | template: ` 14 |
15 | 20 |
21 | `, 22 | data () { 23 | return { 24 | items: getDatas(1000), 25 | item: Item 26 | } 27 | } 28 | }) 29 | 30 | it('check mount', () => { 31 | expect(Instance.name()).toBe('test') 32 | expect(Instance.is('div')).toBe(true) 33 | expect(Instance.isVueInstance()).toBe(true) 34 | expect(Instance.find('.my-list').exists()).toBe(true) 35 | }) 36 | 37 | it('check list build by default', () => { 38 | const vmData = Instance.vm.$data 39 | const vslVm = Instance.find('.my-list').vm 40 | const rootEl = vslVm.$el 41 | expect(rootEl.tagName.toLowerCase()).toBe(VirtualProps.rootTag.default) 42 | 43 | const wrapperEl = rootEl.firstElementChild 44 | 45 | // wrapper element and padding style 46 | expect(wrapperEl.getAttribute('role')).toBe('group') 47 | expect(!!rootEl.style.padding).toBe(false) 48 | expect(!!wrapperEl.style.padding).toBe(true) 49 | expect(wrapperEl.tagName.toLowerCase()).toBe(VirtualProps.wrapTag.default) 50 | 51 | // render number keeps by default 52 | expect(wrapperEl.childNodes.length).toBe(VirtualProps.keeps.default) 53 | 54 | // items render content 55 | for (let i = 0; i < wrapperEl.childNodes.length; i++) { 56 | const itemEl = wrapperEl.childNodes[i] 57 | expect(itemEl.className).toBe('') 58 | expect(itemEl.tagName.toLowerCase()).toBe(VirtualProps.itemTag.default) 59 | 60 | // item inner render (see ./item.vue) 61 | const itemInnerEl = itemEl.firstElementChild 62 | expect(itemInnerEl.className).toBe('inner') 63 | expect(itemInnerEl.querySelector('.index').textContent).toBe(`${i}`) 64 | expect(itemInnerEl.querySelector('.source').textContent).toBe(vmData.items[i].text) 65 | } 66 | }) 67 | }) 68 | -------------------------------------------------------------------------------- /test/element.test.js: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | import VirtualList from '../src/index' 3 | import { VirtualProps } from '../src/props' 4 | import Item from './item.vue' 5 | import { getDatas } from './util' 6 | 7 | describe('element', () => { 8 | const Instance = mount({ 9 | name: 'test', 10 | components: { 11 | 'virtual-list': VirtualList 12 | }, 13 | template: ` 14 |
15 | 26 |
27 | `, 28 | data () { 29 | return { 30 | items: getDatas(1000), 31 | item: Item 32 | } 33 | }, 34 | methods: { 35 | addItemClass (index) { 36 | return 'extra-item-' + index 37 | } 38 | } 39 | }) 40 | 41 | it('check mount', () => { 42 | expect(Instance.name()).toBe('test') 43 | expect(Instance.is('div')).toBe(true) 44 | expect(Instance.isVueInstance()).toBe(true) 45 | expect(Instance.find('.my-list').exists()).toBe(true) 46 | }) 47 | 48 | it('check element tag and class', () => { 49 | const vmData = Instance.vm.$data 50 | const vslVm = Instance.find('.my-list').vm 51 | const rootEl = vslVm.$el 52 | expect(rootEl.tagName.toLowerCase()).toBe('article') 53 | 54 | const wrapperEl = rootEl.firstElementChild 55 | 56 | // wrapper element and padding style 57 | expect(wrapperEl.getAttribute('role')).toBe('group') 58 | expect(!!rootEl.style.padding).toBe(false) 59 | expect(!!wrapperEl.style.padding).toBe(true) 60 | expect(wrapperEl.className).toBe('wrap-class-aaa') 61 | expect(wrapperEl.tagName.toLowerCase()).toBe('section') 62 | 63 | // render number keeps by default 64 | expect(wrapperEl.childNodes.length).toBe(VirtualProps.keeps.default) 65 | 66 | // items render content 67 | for (let i = 0; i < wrapperEl.childNodes.length; i++) { 68 | const itemEl = wrapperEl.childNodes[i] 69 | expect(itemEl.className).toBe(`item-class-bbb extra-item-${i}`) 70 | expect(itemEl.tagName.toLowerCase()).toBe('p') 71 | 72 | // item inner render (see ./item.vue) 73 | const itemInnerEl = itemEl.firstElementChild 74 | expect(itemInnerEl.className).toBe('inner') 75 | expect(itemInnerEl.querySelector('.index').textContent).toBe(`${i}`) 76 | expect(itemInnerEl.querySelector('.source').textContent).toBe(vmData.items[i].text) 77 | } 78 | }) 79 | }) 80 | -------------------------------------------------------------------------------- /test/extra-props.test.js: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | import VirtualList from '../src/index' 3 | import Item from './item.vue' 4 | import { getDatas } from './util' 5 | import Vue from 'vue' 6 | 7 | describe('extra-props', () => { 8 | const Instance = mount({ 9 | name: 'test', 10 | components: { 11 | 'virtual-list': VirtualList 12 | }, 13 | template: ` 14 |
15 | 21 |
22 | `, 23 | data () { 24 | return { 25 | items: getDatas(1000), 26 | item: Item, 27 | extraProps: { 28 | otherProp: '123' 29 | } 30 | } 31 | } 32 | }) 33 | 34 | it('check mount', () => { 35 | expect(Instance.name()).toBe('test') 36 | expect(Instance.is('div')).toBe(true) 37 | expect(Instance.isVueInstance()).toBe(true) 38 | expect(Instance.find('.my-list').exists()).toBe(true) 39 | }) 40 | 41 | it('check extra props render and data reactive', () => { 42 | const vmData = Instance.vm.$data 43 | const vslVm = Instance.find('.my-list').vm 44 | const rootEl = vslVm.$el 45 | const wrapperEl = rootEl.firstElementChild 46 | 47 | const checkProps = (otherProp) => { 48 | // items render content 49 | for (let i = 0; i < wrapperEl.childNodes.length; i++) { 50 | const itemEl = wrapperEl.childNodes[i] 51 | const itemInnerEl = itemEl.firstElementChild 52 | expect(itemInnerEl.className).toBe('inner') 53 | expect(itemInnerEl.querySelector('.index').textContent).toBe(`${i}`) 54 | expect(itemInnerEl.querySelector('.source').textContent).toBe(vmData.items[i].text) 55 | expect(itemInnerEl.querySelector('.other').textContent).toBe(otherProp) 56 | } 57 | } 58 | 59 | checkProps(vmData.extraProps.otherProp) 60 | 61 | vmData.extraProps.otherProp = '789' 62 | Vue.nextTick(() => { 63 | checkProps('789') 64 | }) 65 | }) 66 | }) 67 | -------------------------------------------------------------------------------- /test/extra-props2.test.js: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | import VirtualList from '../src/index' 3 | import Item from './item.vue' 4 | import { getDatas } from './util' 5 | import Vue from 'vue' 6 | 7 | describe('extra-props inline', () => { 8 | const Instance = mount({ 9 | name: 'test', 10 | components: { 11 | 'virtual-list': VirtualList 12 | }, 13 | template: ` 14 |
15 | 21 |
22 | `, 23 | data () { 24 | return { 25 | items: getDatas(1000), 26 | item: Item, 27 | otherProp: 'abc' 28 | } 29 | } 30 | }) 31 | 32 | it('check mount', () => { 33 | expect(Instance.name()).toBe('test') 34 | expect(Instance.is('div')).toBe(true) 35 | expect(Instance.isVueInstance()).toBe(true) 36 | expect(Instance.find('.my-list').exists()).toBe(true) 37 | }) 38 | 39 | it('check extra props render and data reactive', () => { 40 | const vmData = Instance.vm.$data 41 | const vslVm = Instance.find('.my-list').vm 42 | const rootEl = vslVm.$el 43 | const wrapperEl = rootEl.firstElementChild 44 | 45 | const checkProps = (otherProp) => { 46 | // items render content 47 | for (let i = 0; i < wrapperEl.childNodes.length; i++) { 48 | const itemEl = wrapperEl.childNodes[i] 49 | const itemInnerEl = itemEl.firstElementChild 50 | expect(itemInnerEl.className).toBe('inner') 51 | expect(itemInnerEl.querySelector('.index').textContent).toBe(`${i}`) 52 | expect(itemInnerEl.querySelector('.source').textContent).toBe(vmData.items[i].text) 53 | expect(itemInnerEl.querySelector('.other').textContent).toBe(otherProp) 54 | } 55 | } 56 | 57 | checkProps(vmData.otherProp) 58 | 59 | vmData.otherProp = 'xyz' 60 | Vue.nextTick(() => { 61 | checkProps('xyz') 62 | }) 63 | }) 64 | }) 65 | -------------------------------------------------------------------------------- /test/horizontal.test.js: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | import VirtualList from '../src/index' 3 | import Item from './item.vue' 4 | import { getDatas } from './util' 5 | 6 | describe('horizontal', () => { 7 | const Instance = mount({ 8 | name: 'test', 9 | components: { 10 | 'virtual-list': VirtualList 11 | }, 12 | template: ` 13 |
14 | 21 |
22 | `, 23 | data () { 24 | return { 25 | items: getDatas(1000), 26 | item: Item 27 | } 28 | } 29 | }) 30 | 31 | it('check mount', () => { 32 | expect(Instance.name()).toBe('test') 33 | expect(Instance.is('div')).toBe(true) 34 | expect(Instance.isVueInstance()).toBe(true) 35 | expect(Instance.find('.my-list').exists()).toBe(true) 36 | }) 37 | 38 | // @TODO scrollHeight scrollWidth is both 0 39 | it('check scroll direction', () => { 40 | const vslVm = Instance.find('.my-list').vm 41 | const rootEl = vslVm.$el 42 | // const wrapperEl = rootEl.querySelector('[role="group"]') 43 | 44 | expect(rootEl.scrollHeight === rootEl.clientHeight).toBe(true) 45 | // expect(rootEl.scrollWidth > rootEl.clientWidth).toBe(true) 46 | }) 47 | }) 48 | -------------------------------------------------------------------------------- /test/item.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 29 | -------------------------------------------------------------------------------- /test/offset.test.js: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | import VirtualList from '../src/index' 3 | import Item from './item.vue' 4 | import { getDatas } from './util' 5 | import Vue from 'vue' 6 | 7 | describe('offset', () => { 8 | const Instance = mount({ 9 | name: 'test', 10 | components: { 11 | 'virtual-list': VirtualList 12 | }, 13 | template: ` 14 |
15 | 21 |
22 | `, 23 | data () { 24 | return { 25 | items: getDatas(1000), 26 | item: Item, 27 | offset: 0 28 | } 29 | } 30 | }) 31 | 32 | it('check mount', () => { 33 | expect(Instance.name()).toBe('test') 34 | expect(Instance.is('div')).toBe(true) 35 | expect(Instance.isVueInstance()).toBe(true) 36 | expect(Instance.find('.my-list').exists()).toBe(true) 37 | }) 38 | 39 | // @TODO 40 | it('check offset and data reactive', () => { 41 | const vmData = Instance.vm.$data 42 | const vslVm = Instance.find('.my-list').vm 43 | expect(vslVm.virtual.offset).toBe(0) 44 | 45 | vmData.offset = 100 46 | // Vue.nextTick(() => { 47 | // expect(vslVm.virtual.offset).toBe(100) 48 | // }) 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /test/scroll.test.js: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | import VirtualList from '../src/index' 3 | import Item from './item.vue' 4 | import { getDatas } from './util' 5 | import Vue from 'vue' 6 | 7 | describe('scroll', () => { 8 | const Instance = mount({ 9 | name: 'test', 10 | components: { 11 | 'virtual-list': VirtualList 12 | }, 13 | template: ` 14 |
15 | 20 |
21 | `, 22 | data () { 23 | return { 24 | items: getDatas(1000), 25 | item: Item 26 | } 27 | } 28 | }) 29 | 30 | it('check mount', () => { 31 | expect(Instance.name()).toBe('test') 32 | expect(Instance.is('div')).toBe(true) 33 | expect(Instance.isVueInstance()).toBe(true) 34 | expect(Instance.find('.my-list').exists()).toBe(true) 35 | }) 36 | 37 | // @TODO 38 | it('check scroll behavior', () => { 39 | const myList = Instance.find('.my-list') 40 | const vslVm = myList.vm 41 | const rootEl = vslVm.$el 42 | 43 | rootEl.scrollTop = 2000 44 | myList.trigger('scroll') 45 | 46 | Vue.nextTick(() => { 47 | // console.log(vslVm.$data.range.start) 48 | }) 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /test/slot.test.js: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | import VirtualList from '../src/index' 3 | import Item from './item.vue' 4 | import { getDatas } from './util' 5 | 6 | describe('slot', () => { 7 | const Instance = mount({ 8 | name: 'test', 9 | components: { 10 | 'virtual-list': VirtualList 11 | }, 12 | template: ` 13 |
14 | 23 |
Header
24 |
Footer
25 |
26 |
27 | `, 28 | data () { 29 | return { 30 | items: getDatas(1000), 31 | item: Item 32 | } 33 | } 34 | }) 35 | 36 | it('check mount', () => { 37 | expect(Instance.name()).toBe('test') 38 | expect(Instance.is('div')).toBe(true) 39 | expect(Instance.isVueInstance()).toBe(true) 40 | expect(Instance.find('.my-list').exists()).toBe(true) 41 | }) 42 | 43 | it('check slot build', () => { 44 | const vslVm = Instance.find('.my-list').vm 45 | const rootEl = vslVm.$el 46 | const wrapperEl = rootEl.querySelector('[role="group"]') 47 | const headerEl = rootEl.querySelector('[role="header"]') 48 | const footerEl = rootEl.querySelector('[role="footer"]') 49 | 50 | // wrapper shoud be in middle between header and footer 51 | expect(wrapperEl.previousElementSibling).toBe(headerEl) 52 | expect(wrapperEl.nextElementSibling).toBe(footerEl) 53 | 54 | expect(!!headerEl).toBe(true) 55 | expect(!!footerEl).toBe(true) 56 | 57 | expect(headerEl.className).toBe('head1') 58 | expect(headerEl.tagName.toLowerCase()).toBe('section') 59 | expect(headerEl.firstElementChild.textContent).toBe('Header') 60 | 61 | expect(footerEl.className).toBe('foot1') 62 | expect(footerEl.tagName.toLowerCase()).toBe('article') 63 | expect(footerEl.firstElementChild.textContent).toBe('Footer') 64 | }) 65 | }) 66 | -------------------------------------------------------------------------------- /test/start.test.js: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | import VirtualList from '../src/index' 3 | import Item from './item.vue' 4 | import { getDatas } from './util' 5 | import Vue from 'vue' 6 | 7 | describe('start', () => { 8 | const Instance = mount({ 9 | name: 'test', 10 | components: { 11 | 'virtual-list': VirtualList 12 | }, 13 | template: ` 14 |
15 | 21 |
22 | `, 23 | data () { 24 | return { 25 | items: getDatas(1000), 26 | item: Item, 27 | start: 0 28 | } 29 | } 30 | }) 31 | 32 | it('check mount', () => { 33 | expect(Instance.name()).toBe('test') 34 | expect(Instance.is('div')).toBe(true) 35 | expect(Instance.isVueInstance()).toBe(true) 36 | expect(Instance.find('.my-list').exists()).toBe(true) 37 | }) 38 | 39 | // @TODO 40 | it('check start and data reactive', () => { 41 | const vmData = Instance.vm.$data 42 | const vslVm = Instance.find('.my-list').vm 43 | expect(vslVm.virtual.range.start).toBe(0) 44 | 45 | vmData.start = 100 46 | // Vue.nextTick(() => { 47 | // expect(vslVm.virtual.range.start).toBe(100) 48 | // }) 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /test/util.js: -------------------------------------------------------------------------------- 1 | export function getDatas (counts) { 2 | const data = [] 3 | for (let index = 0; index < counts; index++) { 4 | data.push({ 5 | id: String(index), 6 | text: Math.random().toString(16).substr(8) 7 | }) 8 | } 9 | return data 10 | } 11 | --------------------------------------------------------------------------------