├── .github
├── dependabot.yml
└── workflows
│ ├── lint-pr.yml
│ ├── npm-publish.yml
│ └── wc.yml
├── .gitignore
├── .prettierignore
├── .vscode
└── extensions.json
├── LICENSE
├── README.md
├── demo.gif
├── favicon.ico
├── index.html
├── infinite-carousel-wc.js
├── package-lock.json
├── package.json
└── tsconfig.json
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "npm"
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "weekly"
12 | commit-message:
13 | prefix: "fix"
14 | prefix-development: "build"
15 |
--------------------------------------------------------------------------------
/.github/workflows/lint-pr.yml:
--------------------------------------------------------------------------------
1 | name: "Lint PR"
2 | on:
3 | pull_request:
4 | types:
5 | - opened
6 | - edited
7 | - synchronize
8 |
9 | jobs:
10 | conventional-commit-pr-title:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: amannn/action-semantic-pull-request@v1.2.0
14 | env:
15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
16 |
--------------------------------------------------------------------------------
/.github/workflows/npm-publish.yml:
--------------------------------------------------------------------------------
1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
3 |
4 | name: NPM Publish
5 |
6 | on:
7 | release:
8 | types: [created]
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v2
15 | - uses: actions/setup-node@v1
16 | with:
17 | node-version: 12
18 | - run: npm ci
19 |
20 | publish-npm:
21 | needs: build
22 | runs-on: ubuntu-latest
23 | steps:
24 | - uses: actions/checkout@v2
25 | - uses: actions/setup-node@v1
26 | with:
27 | node-version: 12
28 | registry-url: https://registry.npmjs.org/
29 | - run: npm ci
30 | - run: npm publish
31 | env:
32 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
33 |
--------------------------------------------------------------------------------
/.github/workflows/wc.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Build
5 |
6 | on:
7 | push:
8 | branches: [main]
9 | pull_request:
10 | branches: [main]
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - uses: actions/checkout@v2
18 | - uses: actions/setup-node@v1
19 | with:
20 | node-version: 12
21 | - run: npm ci
22 | - run: npm run lint
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.min.js
2 | *.d.ts
3 |
4 | # Created by https://www.gitignore.io/api/node,macos,windows,visualstudiocode
5 | # Edit at https://www.gitignore.io/?templates=node,macos,windows,visualstudiocode
6 |
7 | ### macOS ###
8 | # General
9 | .DS_Store
10 | .AppleDouble
11 | .LSOverride
12 |
13 | # Icon must end with two \r
14 | Icon
15 |
16 | # Thumbnails
17 | ._*
18 |
19 | # Files that might appear in the root of a volume
20 | .DocumentRevisions-V100
21 | .fseventsd
22 | .Spotlight-V100
23 | .TemporaryItems
24 | .Trashes
25 | .VolumeIcon.icns
26 | .com.apple.timemachine.donotpresent
27 |
28 | # Directories potentially created on remote AFP share
29 | .AppleDB
30 | .AppleDesktop
31 | Network Trash Folder
32 | Temporary Items
33 | .apdisk
34 |
35 | ### Node ###
36 | # Logs
37 | logs
38 | *.log
39 | npm-debug.log*
40 | yarn-debug.log*
41 | yarn-error.log*
42 |
43 | # Runtime data
44 | pids
45 | *.pid
46 | *.seed
47 | *.pid.lock
48 |
49 | # Directory for instrumented libs generated by jscoverage/JSCover
50 | lib-cov
51 |
52 | # Coverage directory used by tools like istanbul
53 | coverage
54 |
55 | # nyc test coverage
56 | .nyc_output
57 |
58 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
59 | .grunt
60 |
61 | # Bower dependency directory (https://bower.io/)
62 | bower_components
63 |
64 | # node-waf configuration
65 | .lock-wscript
66 |
67 | # Compiled binary addons (https://nodejs.org/api/addons.html)
68 | build/Release
69 |
70 | # Dependency directories
71 | node_modules/
72 | jspm_packages/
73 |
74 | # TypeScript v1 declaration files
75 | typings/
76 |
77 | # Optional npm cache directory
78 | .npm
79 |
80 | # Optional eslint cache
81 | .eslintcache
82 |
83 | # Optional REPL history
84 | .node_repl_history
85 |
86 | # Output of 'npm pack'
87 | *.tgz
88 |
89 | # Yarn Integrity file
90 | .yarn-integrity
91 |
92 | # dotenv environment variables file
93 | .env
94 |
95 | # parcel-bundler cache (https://parceljs.org/)
96 | .cache
97 |
98 | # next.js build output
99 | .next
100 |
101 | # nuxt.js build output
102 | .nuxt
103 |
104 | # vuepress build output
105 | .vuepress/dist
106 |
107 | # Serverless directories
108 | .serverless
109 |
110 | # FuseBox cache
111 | .fusebox/
112 |
113 | ### VisualStudioCode ###
114 | .vscode/*
115 | !.vscode/settings.json
116 | !.vscode/tasks.json
117 | !.vscode/launch.json
118 | !.vscode/extensions.json
119 |
120 | ### VisualStudioCode Patch ###
121 | # Ignore all local history of files
122 | .history
123 |
124 | ### Windows ###
125 | # Windows thumbnail cache files
126 | Thumbs.db
127 | ehthumbs.db
128 | ehthumbs_vista.db
129 |
130 | # Dump file
131 | *.stackdump
132 |
133 | # Folder config file
134 | [Dd]esktop.ini
135 |
136 | # Recycle Bin used on file shares
137 | $RECYCLE.BIN/
138 |
139 | # Windows Installer files
140 | *.cab
141 | *.msi
142 | *.msix
143 | *.msm
144 | *.msp
145 |
146 | # Windows shortcuts
147 | *.lnk
148 |
149 | # End of https://www.gitignore.io/api/node,macos,windows,visualstudiocode
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.min.js
3 | *.d.ts
4 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
4 |
5 | // List of extensions which should be recommended for users of this workspace.
6 | "recommendations": [
7 | "esbenp.prettier-vscode",
8 | "ritwickdey.liveserver",
9 | "plievone.vscode-template-literal-editor"
10 | ],
11 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace.
12 | "unwantedRecommendations": []
13 | }
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 wes@goulet.dev
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |  [](https://www.webcomponents.org/element/infinite-carousel-wc) [](https://npmjs.org/package/infinite-carousel-wc)
2 |
3 | # infinite-carousel-wc
4 |
5 | An infinite carousel Web Component built with modern CSS and just a little Javascript. Supports both horizontal and vertical scrolling.
6 |
7 | 
8 |
9 | ## [Demo](https://infinite-carousel-wc.netlify.com/)
10 |
11 | ## Installation
12 |
13 | You can integrate infinite-carousel-wc via `
24 |
25 |
26 |
30 | ```
31 |
32 | Now you can use the `infinite-carousel-wc` element anywhere in your html, JSX, template, etc.
33 |
34 | ### Via NPM
35 |
36 | ```bash
37 | npm install infinite-carousel-wc --save
38 | ```
39 |
40 | And then you need to import the module before you can use it in your html/jsx/template:
41 |
42 | ```js
43 | import "infinite-carousel-wc";
44 | ```
45 |
46 | ## Web Component Browser Support
47 |
48 | This web component uses [HTML templates](https://caniuse.com/#feat=template), the [shadow DOM](https://caniuse.com/#feat=shadowdomv1), and [custom elements](https://caniuse.com/#feat=custom-elementsv1). If you need to polyfill for any of these standards then [take a look at the web components polyfill](https://github.com/webcomponents/webcomponentsjs).
49 |
50 | ## Polyfills
51 |
52 | This custom element uses [CSS Scroll Snap](https://caniuse.com/#feat=css-snappoints), [IntersectionObserver](https://caniuse.com/#feat=intersectionobserver) and [Smooth Scrolling](https://caniuse.com/#feat=css-scroll-behavior). Browser support for Scroll Snap is pretty good (this custom element supports both v0 and v1 of the spec). There is a polyfill for [IntersectionObserver](https://github.com/w3c/IntersectionObserver/tree/master/polyfill) and [Smooth Scrolling](https://github.com/iamdustan/smoothscroll).
53 |
54 | A note about IntersectionObserver polyfill on iOS 12.1 and below: you might need to [configure the polyfill to use polling](https://github.com/w3c/IntersectionObserver/tree/master/polyfill#configuring-the-polyfill) for the carousel to work properly.
55 |
56 | ## API and Customization
57 |
58 | ### Slots
59 |
60 | The infinite carousel works by laying out 3 slots, with slot names of `1`, `2`, and `3`. The slots will be re-ordered in a circular manner as the user swipes/scrolls.
61 |
62 | When inifinite-scroller-wc first renders it will show slot 1 as visible (called the "current" position). Slot 2 will be to the right (in the "next" position), and slot 3 will be to the left (in the "previous" position), like this:
63 |
64 | | "previous" | "current" | "next" |
65 | | :--------: | :-------: | :----: |
66 | | 3 | 1 | 2 |
67 |
68 | Remember that "current" is the only slot visible to the user at rest. When the user peaks left or right they will then see the "previous" or "next" slot contents respectively.
69 |
70 | When the user swipes/scrolls forward then some CSS will get added to the slots to re-order them (technically the CSS is added to the slot's parents which are encapsulated in the shadow DOM). After 1 swipe/scroll it will look like this:
71 |
72 | | "previous" | "current" | "next" |
73 | | :--------: | :-------: | :----: |
74 | | 1 | 2 | 3 |
75 |
76 | Another swipe forward/next would produce:
77 |
78 | | "previous" | "current" | "next" |
79 | | :--------: | :-------: | :----: |
80 | | 2 | 3 | 1 |
81 |
82 | Swiping/scrolling backward would do the reverse, as you'd expect.
83 |
84 | The circular re-ordering allows the user to swipe/scroll "infinitely" in either direction. It is up to the consuming code to listen for the [`next` or `previous` events](#events). For an example of listening to the event and updating the slot contents [take a look at index.html](./index.html#L387).
85 |
86 | ### Methods
87 |
88 | - `goNext()`
89 | - Scrolls to the next slot.
90 | - Example: `document.getElementById("carousel").goNext()`
91 | - `goPrevious()`
92 | - Scrolls to the previous slot.
93 | - Example: `document.getElementById("carousel").goPrevious()`
94 | - `reset()`
95 | - Resets the slot order to the initial order. Slot 1 will be in the "current" position, slot 2 will be in the "next" position, and slot 3 will be in the "previous" position.
96 | - Example: `document.getElementById("carousel").reset()`
97 |
98 | ### Attributes/Properties
99 |
100 | - `vertical`
101 | - Add this attribute to make your carousel scroll vertically instead of horizontally.
102 | - Example: ``
103 | - Set the property in Javascript to imperatively set vertical scrolling
104 | - Example: `carousel.vertical = true`
105 | - NOTE: Dynamically changing this attribute/property will cause a reset of the slot order (it calls the `reset()` method).
106 | - `lock`
107 | - Add this attribute to prevent scrolling.
108 | - Example: ``
109 | - Set the property in Javascript to imperatively disable scrolling
110 | - Example: `carousel.lock = true`
111 | - `currentSlot`
112 | - This property will return the current slot being shown.
113 | - You cannot set this property programmatically nor can you set it as an attribute.
114 |
115 | ### Events
116 |
117 | - `next`
118 | - Raised after the user has scrolled to the next slot
119 | - Raised about 200ms before the user is allowed to scroll again
120 | - `event.detail` contains an `ChangeEventDetail` object of the following shape:
121 | ```ts
122 | export interface ChangeEventDetail {
123 | newCurrent: 1 | 2 | 3;
124 | }
125 | ```
126 | - `ChangeEventDetail` is an exported type that you can consume if you are writing your code in Typescript.
127 | - Example: `carousel.addEventListener("next", handleCarouselNext())`
128 | - `previous`
129 | - Raised after the user has scrolled to the previous slot
130 | - Raised about 200ms before the user is allowed to scroll again
131 | - `event.detail` contains an `ChangeEventDetail` object of the following shape:
132 | ```ts
133 | export interface ChangeEventDetail {
134 | newCurrent: 1 | 2 | 3;
135 | }
136 | ```
137 | - `ChangeEventDetail` is an exported type that you can consume if you are writing your code in Typescript.
138 | - Example: `carousel.addEventListener("previous", handleCarouselPrevious())`
139 |
140 | ### Styling
141 |
142 | You can style the infinite-carousel-wc element as you would any regular element, in CSS. You can see [an example in index.html](./index.html#L59).
143 |
144 | ## Contribute
145 |
146 | This project is built with standard HTML/CSS/JS, no frameworks or special web-component compilers here (for maximum simplicity and minimum size). If you want to learn more about writing custom elements see [MDN](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) or [this web fundamentals page](https://developers.google.com/web/fundamentals/web-components/).
147 |
148 | The source for this web component is contained in [infinite-carousel-wc.js](infinite-carousel-wc.js) and example usage is in [index.html](index.html). To debug/run the example you can just open index.html in a browser. For a hot-reload developer experience try [using live server in vscode](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer).
149 |
150 | You will need the dev dependencies of this project installed to run the post-commit hooks.
151 |
152 | ```bash
153 | npm install
154 | ```
155 |
--------------------------------------------------------------------------------
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wes-goulet/infinite-carousel-wc/870af36b467a81630c1c5963ff58345a2b3dbd26/demo.gif
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wes-goulet/infinite-carousel-wc/870af36b467a81630c1c5963ff58345a2b3dbd26/favicon.ico
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 | infinite-carousel-wc - An infinite carousel Web Component built with
11 | modern CSS and just a little Javascript
12 |
13 |
14 |
15 |
16 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
250 |
251 |
252 |
253 |
254 |
255 |
infinite-carousel-wc
256 |
257 | An infinite carousel Web Component built with modern CSS and just a
258 | little Javascript
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
275 |
276 |
277 |
278 |
279 |
280 | This small, simple web component has no dependencies and can be
281 | consumed by vanilla JS or any front-end framework. It is implemented
282 | using modern CSS and Javascript standards for maximum performance.
283 |
284 |
285 |
286 |
293 | Lightweight (~1.5kb gzipped)
294 |
295 |
296 |
303 | No Dependencies
304 |
305 |
306 |
313 | CSS re-ordering instead of JS DOM manipulation
314 |