├── .eslintignore ├── .gitattributes ├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── .nojekyll ├── .npmignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── README_CN.md ├── README_KO.md ├── dist ├── components │ ├── Swipe.svelte │ ├── Swipe.svelte.d.ts │ ├── SwipeItem.svelte │ └── SwipeItem.svelte.d.ts ├── helpers │ ├── SwipeSnap.d.ts │ └── SwipeSnap.js ├── index.d.ts └── index.js ├── jsconfig.json ├── package-lock.json ├── package.json ├── src ├── app.html ├── lib │ ├── components │ │ ├── Swipe.svelte │ │ └── SwipeItem.svelte │ ├── helpers │ │ └── SwipeSnap.js │ └── index.js └── routes │ ├── +layout.js │ └── +page.svelte ├── static ├── favicon.png └── images │ ├── 1.jpg │ ├── 2.jpg │ ├── 3.jpg │ ├── 4.jpg │ ├── 5.jpg │ ├── dy-1.jpg │ ├── dy-2.jpg │ ├── dy-3.jpg │ ├── dy-4.jpg │ ├── dy-5.jpg │ └── url-code.png ├── svelte-swipe.png ├── svelte.config.js └── vite.config.js /.eslintignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Deploy to GitHub Pages 3 | 4 | on: 5 | push: 6 | branches: [ master ] 7 | workflow_dispatch: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: Install dependencies and build 16 | run: | 17 | npm install 18 | npm run build 19 | 20 | - name: Create .nojekyll file 21 | run: touch build/.nojekyll 22 | 23 | - name: Deploy to GitHub Pages 24 | uses: JamesIves/github-pages-deploy-action@4.1.5 25 | with: 26 | branch: gh-pages 27 | folder: build -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | debug.log 10 | -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SharifClick/svelte-swipe/7a771f0a0724452116eca1600817cbe611d15a62/.nojekyll -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | dev 2 | docs 3 | node_modules -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "none", 4 | "printWidth": 100, 5 | "plugins": ["prettier-plugin-svelte"], 6 | "pluginSearchDirs": ["."], 7 | "overrides": [ 8 | { 9 | "files": "*.svelte", 10 | "options": { "parser": "svelte" } 11 | }, 12 | { 13 | "files": "*.md", 14 | "options": { "requirePragma": true } 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # svelte-swipe changelog 2 | 3 | ## 1.0.0 4 | 5 | * First release 6 | * Supports indicators 7 | * Supports autoplay 8 | 9 | ## 1.1.0 10 | 11 | * Fix indicators position 12 | * Fix transition duration 13 | * Fix autoplay 14 | 15 | ## 1.2.0 16 | 17 | * Swipe item wrapper 18 | 19 | ## 1.2.1 20 | 21 | * bug fixes 22 | 23 | ## 1.2.2 24 | 25 | * style bug fixes 26 | * doc fixes 27 | * license is MIT 28 | 29 | ## 1.3.0 30 | 31 | * fix issue #2 32 | * bug fixes #2 33 | * doc fixes 34 | 35 | ## 1.3.1 36 | 37 | * fix issue #4 38 | 39 | ## 1.3.2 40 | 41 | * fix issue #6 42 | 43 | ## 1.4.0 44 | 45 | * add feat of issue #7 46 | 47 | ## 1.5.0 48 | 49 | * add feat of issue #8 50 | * bug fixes 51 | * exposed `goTo` method from root component 52 | 53 | ## 1.6.0 54 | 55 | * fix `goTo` logic to navigate slide by given slide index 56 | * exposed `nextItem`, `prevItem` method to move slide prev/next from outside 57 | * new readonly prop `active_item` for tracking active slide index 58 | * bug fixes 59 | * doc fixes 60 | 61 | ## 1.6.1 62 | 63 | * merge PR #14 64 | * closed issue #10, #14 65 | * doc fixes 66 | 67 | ## 1.7.0 68 | 69 | * feature request issue #18 (added vertical swipe support) 70 | * doc fixes 71 | 72 | ## 1.7.1 73 | 74 | * merged pr #26 75 | * fixed issue #25 76 | 77 | ## 1.7.2 78 | 79 | * merged pr #31 80 | * fixed page scroll on swipe issue 81 | 82 | ## 1.8.0 83 | 84 | * merged pr #42 85 | * Fixed issue #27 86 | * Allow Swipe height automatically follow the SwipeItem children 87 | 88 | ## 1.8.1 89 | 90 | * merged pr #44 91 | * Fixed issue #43 92 | * Allow default scroll on touch (for mobile) 93 | 94 | ## 1.8.2 95 | * Fixed issue #51 96 | 97 | 98 | ## 1.9.0 99 | * issue #34 resolved 100 | * Sveltekit featuring dev environment 101 | * Publish gh-pages 102 | * bugfixes 103 | 104 | ## 1.9.1 105 | * Fix module import path 106 | 107 | ## 1.9.2 108 | * Issue #55 - fix initial item shift issue (Thanks to @gnuletik) 109 | * Fix infinite play 110 | * Fix exports path on package file 111 | * Demo fix 112 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2019 Sharif Ahmed me.sharifahmed@gmail.com sharifahmed.me 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | Svelte Swipe 3 |
4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | ____ 21 | 22 | [English](README.md) / [Korean](README_KO.md) / [简体中文](README_CN.md) 23 | 24 | Swipable items wrapper component for Svelte :fire: :boom: (zero dependencies) 25 | 26 | ## 🚀[See it in Action](https://sharifclick.github.io/svelte-swipe/) 27 | 28 | 29 | ## Installation 30 | 31 | ```bash 32 | npm i -D svelte-swipe 33 | ``` 34 | 35 | ## Usage 36 | 37 | ```html 38 | 50 | 51 | 61 | 62 |
63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |
81 | 82 | ``` 83 | 84 | ### Supports Dynamic height (from child) 🔥 85 | 86 | ```html 87 | 88 | 96 | 97 |
98 | 99 | {#each items as item, i} 100 | 104 | .... 105 | 106 | {/each} 107 | 108 |
109 | 110 | ``` 111 | ### Supports Infinite swipe 🔥 112 | 113 | ```html 114 | 115 | 116 |
117 | 118 | {#each items as item, i} 119 | 122 | .... 123 | 124 | {/each} 125 | 126 |
127 | 128 | ``` 129 | ### Vertical Swipe 🔥 130 | 131 | ```html 132 | 133 |
134 | 135 | 136 | ... 137 | 138 | ... 139 | 140 |
141 | ``` 142 | 143 | ### Pointer event inside Swipe Item 144 | 145 | ```html 146 | 153 | 154 |
155 | 156 | 157 |
158 | 159 |
160 |
161 | ... 162 |
163 |
164 | 165 | ``` 166 | 167 | 168 | ### Programmatically change slides 169 | 170 | ```html 171 | 172 | 184 |
185 | 186 | .... 187 | ... 188 | 189 |
190 |
191 | 192 | 193 |
194 | ``` 195 | 196 | ### Supports custom thumbnail 197 | ## 🚀[See example with custom thumbnail](https://svelte.dev/repl/be477862ac8b4dfea4c8e454e556ef2c?version=3.20.1) 198 | ```html 199 | 200 | 208 |
209 | 210 | .... 211 | ... 212 | 213 |
214 | 215 | ``` 216 | 217 | ## Default css custom properties 218 | 219 | ```css 220 | 221 | :root{ 222 | --sv-swipe-panel-height: inherit; 223 | --sv-swipe-panel-width: inherit; 224 | --sv-swipe-panel-wrapper-index: 2; 225 | --sv-swipe-indicator-active-color: grey; 226 | --sv-swipe-handler-top: 0px; 227 | } 228 | 229 | ``` 230 | 231 | ## Props 232 | 233 | | Name | Type | Description | Required | Default | 234 | | --- | --- | --- | --- | --- | 235 | | `is_vertical` | `Boolean` | allow swipe items vertically | No | `false` | 236 | | `autoplay` | `Boolean` | Play items as slide | No | `false` | 237 | | `showIndicators` | `Boolean` | appears clickable circle indicators bottom center of item | No | `false` | 238 | | `transitionDuration` | `Number` | staying duration of per slide/swipe item | No | `200` *ms | 239 | | `delay` | `Number` | transition delay | No | `1000` *ms | 240 | | `defaultIndex` | `Number` | initial item index | No |`0` | 241 | | `allow_dynamic_height` | `Boolean` | allow firing height change event `on:swipe_item_height_change` | No |`false` | 242 | | `allow_infinite_swipe` | `Boolean` | allow swipe items infinitely | No |`false` | 243 | | `active` | `Boolean` | fire height change event | No |`false` | 244 | 245 | ## Events 246 | 247 | | Name | Description | Component | 248 | | --- | --- | --- | 249 | | `on:change` | fires on swipe-end with with holding detail `active_item`, `swipe_direction` and `active_element` | `Swipe` | 250 | | `on:swipe_item_height_change` | fires on swipe-end with holding child's current height detail | `SwipeItem` | 251 | 252 | 253 | ## NPM Statistics 254 | 255 | Download stats for this NPM package 256 | 257 | [![NPM](https://nodei.co/npm/svelte-swipe.png)](https://nodei.co/npm/svelte-swipe/) 258 | 259 | ### Scan qr code to see url in your device 260 | 261 | ![demo-url](https://github.com/SharifClick/svelte-swipe/blob/master/static/images/url-code.png) 262 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # Svelte Swipe 2 | 3 |

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |

17 | 18 | Svelte 轮播组件 :fire: :boom: (无依赖文件 - 压缩后仅 3.37 KB ) 19 | 20 | ## 🚀[查看示例](https://sharifclick.github.io/svelte-swipe/) 21 | 22 | 23 | ## 如何安装 24 | 25 | ```bash 26 | npm i -D svelte-swipe 27 | ``` 28 | 29 | ## 使用方法 30 | 31 | ```html 32 | 44 | 45 | 55 | 56 |
57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |
75 | 76 | ``` 77 | ### 垂直轮播 🔥 78 | 79 | ```html 80 | 81 |
82 | 83 | 84 | ... 85 | 86 | ... 87 | 88 |
89 | ``` 90 | 91 | ### 轮播项的点击事件 92 | 93 | ```html 94 | 101 | 102 |
103 | 104 | 105 |
106 | 107 |
108 |
109 | ... 110 |
111 |
112 | 113 | ``` 114 | 115 | 116 | ### 自定义的轮播方式 117 | 118 | ```html 119 | 120 | 132 |
133 | 134 | .... 135 | ... 136 | 137 |
138 |
139 | 140 | 141 |
142 | ``` 143 | 144 | ### 支持自定义轮播缩略图 145 | ## 🚀[查看带有自定义轮播图的示例](https://svelte.dev/repl/be477862ac8b4dfea4c8e454e556ef2c?version=3.20.1) 146 | ```html 147 | 148 | 156 |
157 | 158 | .... 159 | ... 160 | 161 |
162 | 163 | ``` 164 | 165 | ## 默认的css自定义属性 166 | 167 | ```css 168 | 169 | :root{ 170 | --sv-swipe-panel-height: inherit; 171 | --sv-swipe-panel-width: inherit; 172 | --sv-swipe-panel-wrapper-index: 2; 173 | --sv-swipe-indicator-active-color: grey; 174 | --sv-swipe-handler-top: 0px; 175 | } 176 | 177 | ``` 178 | 179 | ## 配置项 180 | 181 | | 名称 | 类型 | 描述 | 必填 | 默认值 | 182 | | --- | --- | --- | --- | --- | 183 | | `is_vertical` | `Boolean` | 允许轮播图垂直滑动 | No | `false` | 184 | | `autoplay` | `Boolean` | 允许轮播图自动滚动 | No | `false` | 185 | | `showIndicators` | `Boolean` | 在轮播图下方显示默认轮播指示器 | No | `false` | 186 | | `transitionDuration` | `Number` | 轮播图动画过渡时间 | No | `200` 毫秒 | 187 | | `delay` | `Number` | 轮播图自动滚动等待时间 | No | `1000` 毫秒 | 188 | | `defaultIndex` | `Number` | 轮播图初始位置索引 | No |`0` | 189 | 190 | ## NPM 统计 191 | 192 | 此 NPM 包的统计信息 193 | 194 | [![NPM](https://nodei.co/npm/svelte-swipe.png)](https://nodei.co/npm/svelte-swipe/) 195 | 196 | ### 扫描二维码在手机端查看示例效果 197 | 198 | ![demo-url](https://github.com/SharifClick/svelte-swipe/blob/master/docs/images/url-code.png) 199 | -------------------------------------------------------------------------------- /README_KO.md: -------------------------------------------------------------------------------- 1 | # Svelte Swipe 2 | 3 |

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |

17 | 18 | Svelte의 스와이프 가능한 wrapper 컴포넌트 :fire: :boom: 19 | 20 | ## 🚀[동작 예시 보기](https://sharifclick.github.io/svelte-swipe/) 21 | 22 | ## 설치 23 | 24 | ```bash 25 | npm i -D svelte-swipe 26 | ``` 27 | 28 | ## 사용 29 | 30 | ```html 31 | 42 | 43 | 53 | 54 |
55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 |
73 | ``` 74 | 75 | ### 수직 스와이프 🔥 76 | 77 | ```html 78 |
79 | 80 | 81 | ... 82 | 83 | ... 84 | 85 |
86 | ``` 87 | 88 | ### 스와이프 항목 내부의 포인터 이벤트 89 | 90 | ```html 91 | 96 | 97 |
98 | 99 | 100 |
101 | 102 |
103 |
104 | ... 105 |
106 |
107 | ``` 108 | 109 | ### 코드로 슬라이드 바꾸기 110 | 111 | ```html 112 | 123 |
124 | 125 | .... 126 | ... 127 | 128 |
129 |
130 | 131 | 132 |
133 | ``` 134 | 135 | ## 기본 css 커스텀 값들 136 | 137 | ```css 138 | :root { 139 | --sv-swipe-panel-height: inherit; 140 | --sv-swipe-panel-width: inherit; 141 | --sv-swipe-panel-wrapper-index: 2; 142 | --sv-swipe-indicator-active-color: grey; 143 | --sv-swipe-handler-top: 0px; 144 | } 145 | ``` 146 | 147 | ## Config에 넘길 수 있는 인자 148 | 149 | | Name | Type | Description | Required | Default | 150 | | -------------------- | --------- | ------------------------------------------------------- | -------- | ----------- | 151 | | `is_vertical` | `Boolean` | 수직 방향으로 swipe 여부 | No | `false` | 152 | | `autoplay` | `Boolean` | 항목 자동 슬라이드 여부 | No | `false` | 153 | | `showIndicators` | `Boolean` | 중앙 하단에 클릭 가능한 원형의 인디케이터 표시 여부 | No | `false` | 154 | | `transitionDuration` | `Number` | 각 스와이프 아이템으로 넘어가는 데 걸리는 시간 | No | `200` \*ms | 155 | | `delay` | `Number` | autoplay 활성화 시 다음 아이템으로 스와이프 하는 딜레이 | No | `1000` \*ms | 156 | | `defaultIndex` | `Number` | 가장 먼저 표시할 기본 항목의 인덱스 | No | `0` | 157 | 158 | ## NPM 통계 159 | 160 | 이 NPM 패키지에 대한 통계 다운로드 161 | 162 | [![NPM](https://nodei.co/npm/svelte-swipe.png)](https://nodei.co/npm/svelte-swipe/) 163 | 164 | ### QR 코드를 스캔하여 URL 확인 165 | 166 | ![데모-url](https://github.com/SharifClick/svelte-swipe/blob/master/docs/images/url-code.png) 167 | -------------------------------------------------------------------------------- /dist/components/Swipe.svelte: -------------------------------------------------------------------------------- 1 | 159 | 160 |
161 |
162 |
163 | 164 |
165 |
166 | 167 | 194 | 195 | 247 | -------------------------------------------------------------------------------- /dist/components/Swipe.svelte.d.ts: -------------------------------------------------------------------------------- 1 | /** @typedef {typeof __propDef.props} SwipeProps */ 2 | /** @typedef {typeof __propDef.events} SwipeEvents */ 3 | /** @typedef {typeof __propDef.slots} SwipeSlots */ 4 | export default class Swipe extends SvelteComponentTyped<{ 5 | is_vertical?: boolean | undefined; 6 | active_item?: number | undefined; 7 | allow_infinite_swipe?: boolean | undefined; 8 | goTo?: ((step: any) => void) | undefined; 9 | nextItem?: (() => void) | undefined; 10 | prevItem?: (() => void) | undefined; 11 | transitionDuration?: number | undefined; 12 | showIndicators?: boolean | undefined; 13 | autoplay?: boolean | undefined; 14 | delay?: number | undefined; 15 | defaultIndex?: number | undefined; 16 | pause_on_hover?: boolean | undefined; 17 | }, { 18 | change: CustomEvent; 19 | } & { 20 | [evt: string]: CustomEvent; 21 | }, { 22 | default: {}; 23 | }> { 24 | get active_item(): number; 25 | get pause_on_hover(): NonNullable; 26 | get goTo(): (step: any) => void; 27 | get prevItem(): () => void; 28 | get nextItem(): () => void; 29 | } 30 | export type SwipeProps = typeof __propDef.props; 31 | export type SwipeEvents = typeof __propDef.events; 32 | export type SwipeSlots = typeof __propDef.slots; 33 | import { SvelteComponentTyped } from "svelte"; 34 | declare const __propDef: { 35 | props: { 36 | is_vertical?: boolean | undefined; 37 | active_item?: number | undefined; 38 | allow_infinite_swipe?: boolean | undefined; 39 | goTo?: ((step: any) => void) | undefined; 40 | nextItem?: (() => void) | undefined; 41 | prevItem?: (() => void) | undefined; 42 | transitionDuration?: number | undefined; 43 | showIndicators?: boolean | undefined; 44 | autoplay?: boolean | undefined; 45 | delay?: number | undefined; 46 | defaultIndex?: number | undefined; 47 | pause_on_hover?: boolean | undefined; 48 | }; 49 | events: { 50 | change: CustomEvent; 51 | } & { 52 | [evt: string]: CustomEvent; 53 | }; 54 | slots: { 55 | default: {}; 56 | }; 57 | }; 58 | export {}; 59 | -------------------------------------------------------------------------------- /dist/components/SwipeItem.svelte: -------------------------------------------------------------------------------- 1 | 29 | 30 |
35 |
36 | 37 |
38 |
39 | 40 | 56 | -------------------------------------------------------------------------------- /dist/components/SwipeItem.svelte.d.ts: -------------------------------------------------------------------------------- 1 | /** @typedef {typeof __propDef.props} SwipeItemProps */ 2 | /** @typedef {typeof __propDef.events} SwipeItemEvents */ 3 | /** @typedef {typeof __propDef.slots} SwipeItemSlots */ 4 | export default class SwipeItem extends SvelteComponentTyped<{ 5 | style?: string | undefined; 6 | active?: boolean | undefined; 7 | classes?: string | undefined; 8 | allow_dynamic_height?: boolean | undefined; 9 | }, { 10 | swipe_item_height_change: CustomEvent; 11 | } & { 12 | [evt: string]: CustomEvent; 13 | }, { 14 | default: {}; 15 | }> { 16 | } 17 | export type SwipeItemProps = typeof __propDef.props; 18 | export type SwipeItemEvents = typeof __propDef.events; 19 | export type SwipeItemSlots = typeof __propDef.slots; 20 | import { SvelteComponentTyped } from "svelte"; 21 | declare const __propDef: { 22 | props: { 23 | style?: string | undefined; 24 | active?: boolean | undefined; 25 | classes?: string | undefined; 26 | allow_dynamic_height?: boolean | undefined; 27 | }; 28 | events: { 29 | swipe_item_height_change: CustomEvent; 30 | } & { 31 | [evt: string]: CustomEvent; 32 | }; 33 | slots: { 34 | default: {}; 35 | }; 36 | }; 37 | export {}; 38 | -------------------------------------------------------------------------------- /dist/helpers/SwipeSnap.d.ts: -------------------------------------------------------------------------------- 1 | export default SwipeSnap; 2 | /** 3 | * A class for creating a swipeable carousel with snapping behavior. 4 | * @class 5 | */ 6 | declare class SwipeSnap { 7 | /** 8 | * Creates an instance of SwipeSnap. 9 | * @constructor 10 | * @param {Object} [options={}] - Options for configuring the SwipeSnap carousel. 11 | * @param {HTMLElement} [options.element] - The HTML element that contains the carousel. 12 | * @param {boolean} [options.is_vertical=false] - Whether the carousel is vertical (true) or horizontal (false). 13 | * @param {number} [options.transition_duration=300] - The duration of the transition animation in milliseconds. 14 | * @param {boolean} [options.allow_infinite_swipe=false] - Whether to allow infinite looping of carousel items. 15 | */ 16 | constructor(options?: { 17 | element?: HTMLElement | undefined; 18 | is_vertical?: boolean | undefined; 19 | transition_duration?: number | undefined; 20 | allow_infinite_swipe?: boolean | undefined; 21 | } | undefined); 22 | element: HTMLElement | undefined; 23 | wrapper: Element | null; 24 | handler: Element | null; 25 | elements: NodeListOf; 26 | elements_count: number; 27 | is_vertical: boolean | undefined; 28 | transition_duration: number | undefined; 29 | allow_infinite_swipe: boolean | undefined; 30 | pos_axis: number; 31 | page_axis: string; 32 | axis: number; 33 | long_touch: boolean; 34 | last_axis_pos: number; 35 | default_index: number; 36 | active_indicator: number; 37 | active_item: number; 38 | touch_active: boolean; 39 | SWIPE: (event: any) => void; 40 | SWIPE_START: (event: any) => void; 41 | SWIPE_END: (event: any) => void; 42 | /** 43 | * Prevents the default behavior of an event. 44 | * @param {Event} event - The event to prevent. 45 | */ 46 | prevent(event: Event): void; 47 | update(): void; 48 | available_space: any; 49 | available_distance: any; 50 | available_measure: number | undefined; 51 | setInfiniteSwipe(): void; 52 | setElementsPosition({ elems, available_space, pos_axis, has_infinite_loop, distance, moving, init, end, reset }: { 53 | elems?: any[] | undefined; 54 | available_space?: number | undefined; 55 | pos_axis?: number | undefined; 56 | has_infinite_loop?: boolean | undefined; 57 | distance?: number | undefined; 58 | moving?: boolean | undefined; 59 | init?: boolean | undefined; 60 | end?: boolean | undefined; 61 | reset?: boolean | undefined; 62 | }): void; 63 | generateTranslateValue(value: any): string; 64 | generateTouchPosCss(value: any, touch_end?: boolean): string; 65 | swipeStart(event: any): void; 66 | swipe(event: any): void; 67 | swipeEnd(event: any): void; 68 | eventDelegate(type: any): void; 69 | changeItem(item: any): void; 70 | goTo(step: any): void; 71 | prevItem(): void; 72 | nextItem(): void; 73 | /** 74 | * Retrieves the current properties of the carousel. 75 | * @returns {Object} Carousel properties object. 76 | * @property {number} elements_count - The total number of carousel items. 77 | * @property {number} active_item - The index of the currently active carousel item. 78 | * @property {HTMLElement} active_element - The currently active carousel item element. 79 | */ 80 | getProps(): Object; 81 | } 82 | -------------------------------------------------------------------------------- /dist/helpers/SwipeSnap.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | /** 3 | * A class for creating a swipeable carousel with snapping behavior. 4 | * @class 5 | */ 6 | 7 | class SwipeSnap { 8 | /** 9 | * Creates an instance of SwipeSnap. 10 | * @constructor 11 | * @param {Object} [options={}] - Options for configuring the SwipeSnap carousel. 12 | * @param {HTMLElement} [options.element] - The HTML element that contains the carousel. 13 | * @param {boolean} [options.is_vertical=false] - Whether the carousel is vertical (true) or horizontal (false). 14 | * @param {number} [options.transition_duration=300] - The duration of the transition animation in milliseconds. 15 | * @param {boolean} [options.allow_infinite_swipe=false] - Whether to allow infinite looping of carousel items. 16 | */ 17 | 18 | constructor(options = {}) { 19 | this.element = options.element; 20 | this.wrapper = this.element.querySelector('.swipeable-slot-wrapper'); 21 | this.handler = this.element.querySelector('.swipe-handler'); 22 | this.elements = this.wrapper.querySelectorAll('.swipeable-item'); 23 | this.elements_count = this.elements.length; 24 | 25 | this.is_vertical = options.is_vertical; 26 | this.transition_duration = options.transition_duration; 27 | this.allow_infinite_swipe = options.allow_infinite_swipe; 28 | 29 | this.pos_axis = 0; 30 | this.page_axis = options.is_vertical ? 'pageY' : 'pageX'; 31 | this.axis = 0; 32 | this.long_touch = false; 33 | this.last_axis_pos = 0; 34 | this.default_index = 0; 35 | this.active_indicator = 0; 36 | this.active_item = 0; 37 | this.touch_active = false; 38 | 39 | if (options.allow_infinite_swipe) { 40 | this.setInfiniteSwipe(); 41 | } 42 | 43 | this.SWIPE = this.swipe.bind(this); 44 | this.SWIPE_START = this.swipeStart.bind(this); 45 | this.SWIPE_END = this.swipeEnd.bind(this); 46 | 47 | this.handler.addEventListener('touchstart', this.SWIPE_START); 48 | this.handler.addEventListener('mousedown', this.SWIPE_START); 49 | } 50 | 51 | /** 52 | * Prevents the default behavior of an event. 53 | * @param {Event} event - The event to prevent. 54 | */ 55 | 56 | prevent(event) { 57 | if (event && event.cancelable) { 58 | event.preventDefault(); 59 | } 60 | event && event.stopImmediatePropagation(); 61 | event && event.stopPropagation(); 62 | } 63 | 64 | update() { 65 | let { offsetWidth, offsetHeight } = this.wrapper; 66 | this.available_space = this.is_vertical ? offsetHeight : offsetWidth; 67 | 68 | this.setElementsPosition({ 69 | init: true, 70 | elems: [...this.elements], 71 | available_space: this.available_space, 72 | has_infinite_loop: this.allow_infinite_swipe 73 | }); 74 | 75 | this.available_distance = 0; 76 | this.available_measure = this.available_space * (this.elements_count - 1); 77 | if (this.default_index) { 78 | this.changeItem(this.default_index); 79 | } 80 | } 81 | 82 | setInfiniteSwipe() { 83 | this.wrapper.prepend(this.elements[this.elements_count - 1].cloneNode(true)); 84 | this.wrapper.append(this.elements[0].cloneNode(true)); 85 | this.elements = this.element.querySelectorAll('.swipeable-item'); 86 | } 87 | 88 | setElementsPosition({ 89 | elems = [], 90 | available_space = 0, 91 | pos_axis = 0, 92 | has_infinite_loop = false, 93 | distance = 0, 94 | moving = false, 95 | init = false, 96 | end = false, 97 | reset = false 98 | }) { 99 | elems.forEach((element, i) => { 100 | let idx = has_infinite_loop ? i - 1 : i; 101 | if (init) { 102 | element.style.transform = this.generateTranslateValue(available_space * idx); 103 | element.classList.remove('is-item-hidden'); 104 | } 105 | if (moving) { 106 | element.style.cssText = this.generateTouchPosCss(available_space * idx - distance); 107 | } 108 | if (end) { 109 | element.style.cssText = this.generateTouchPosCss(available_space * idx - pos_axis, true); 110 | } 111 | if (reset) { 112 | element.style.cssText = this.generateTouchPosCss(available_space * idx - pos_axis); 113 | } 114 | }); 115 | } 116 | 117 | generateTranslateValue(value) { 118 | return this.is_vertical ? `translate3d(0, ${value}px, 0)` : `translate3d(${value}px, 0, 0)`; 119 | } 120 | 121 | generateTouchPosCss(value, touch_end = false) { 122 | let transform_string = this.generateTranslateValue(value); 123 | let _css = ` 124 | -webkit-transition-duration: ${touch_end ? this.transition_duration : '0'}ms; 125 | transition-duration: ${touch_end ? this.transition_duration : '0'}ms; 126 | -webkit-transform: ${transform_string}; 127 | -ms-transform: ${transform_string};`; 128 | return _css; 129 | } 130 | 131 | swipeStart(event) { 132 | this.prevent(event); 133 | this.touch_active = true; 134 | this.long_touch = false; 135 | setTimeout(() => { 136 | this.long_touch = true; 137 | }, 250); 138 | this.axis = event.touches ? event.touches[0][this.page_axis] : event[this.page_axis]; 139 | this.eventDelegate('add'); 140 | } 141 | 142 | swipe(event) { 143 | if (this.touch_active) { 144 | this.prevent(event); 145 | let axis = event.touches ? event.touches[0][this.page_axis] : event[this.page_axis]; 146 | let distance = this.axis - axis + this.pos_axis; 147 | if (!this.allow_infinite_swipe) { 148 | if ( 149 | (this.pos_axis == 0 && this.axis < axis) || 150 | (this.pos_axis == this.available_measure && this.axis > axis) 151 | ) { 152 | return; 153 | } 154 | } 155 | event.preventDefault(); 156 | 157 | // if (distance <= availableMeasure && distance >= 0) { 158 | // } 159 | this.setElementsPosition({ 160 | moving: true, 161 | elems: [...this.elements], 162 | available_space: this.available_space, 163 | has_infinite_loop: this.allow_infinite_swipe, 164 | distance 165 | }); 166 | this.available_distance = distance; 167 | this.last_axis_pos = axis; 168 | } 169 | } 170 | 171 | swipeEnd(event) { 172 | this.prevent(event); 173 | let direction = this.axis < this.last_axis_pos; 174 | this.touch_active = false; 175 | let available_space = this.available_space; 176 | let accidental_touch = 177 | Math.round(this.available_space / 50) > Math.abs(this.axis - this.last_axis_pos); 178 | if (this.long_touch || accidental_touch) { 179 | this.available_distance = 180 | Math.round(this.available_distance / available_space) * available_space; 181 | } else { 182 | this.available_distance = direction 183 | ? Math.floor(this.available_distance / available_space) * available_space 184 | : Math.ceil(this.available_distance / available_space) * available_space; 185 | } 186 | this.axis = null; 187 | this.last_axis_pos = null; 188 | this.pos_axis = this.available_distance; 189 | this.active_indicator = this.available_distance / available_space; 190 | this.active_item = this.active_indicator; 191 | this.default_index = this.active_item; 192 | 193 | this.setElementsPosition({ 194 | end: true, 195 | elems: [...this.elements], 196 | available_space: available_space, 197 | pos_axis: this.pos_axis, 198 | has_infinite_loop: this.allow_infinite_swipe 199 | }); 200 | 201 | if (this.allow_infinite_swipe) { 202 | if (this.active_item === -1) { 203 | this.pos_axis = available_space * (this.elements_count - 1); 204 | } 205 | if (this.active_item === this.elements_count) { 206 | this.pos_axis = 0; 207 | } 208 | this.active_indicator = this.pos_axis / available_space; 209 | this.active_item = this.active_indicator; 210 | this.default_index = this.active_item; 211 | 212 | setTimeout(() => { 213 | this.setElementsPosition({ 214 | reset: true, 215 | elems: [...this.elements], 216 | available_space: available_space, 217 | pos_axis: this.pos_axis, 218 | has_infinite_loop: this.allow_infinite_swipe 219 | }); 220 | }, this.transition_duration); 221 | } 222 | let swipe_direction = direction ? 'right' : 'left'; 223 | this.eventDelegate('remove'); 224 | 225 | const customEvent = new CustomEvent('swipe_end', { 226 | detail: { 227 | active_item: this.active_item, 228 | swipe_direction, 229 | active_element: this.elements[this.active_item] 230 | } 231 | }); 232 | 233 | this.element.dispatchEvent(customEvent); 234 | } 235 | 236 | eventDelegate(type) { 237 | let delegationTypes = { 238 | add: 'addEventListener', 239 | remove: 'removeEventListener' 240 | }; 241 | if (typeof window !== 'undefined') { 242 | window[delegationTypes[type]]('mousemove', this.SWIPE); 243 | window[delegationTypes[type]]('mouseup', this.SWIPE_END); 244 | window[delegationTypes[type]]('touchmove', this.SWIPE, { passive: false }); 245 | window[delegationTypes[type]]('touchend', this.SWIPE_END, { passive: false }); 246 | } 247 | } 248 | 249 | changeItem(item) { 250 | let max = this.available_space; 251 | this.available_distance = max * item; 252 | this.active_indicator = item; 253 | this.swipeEnd(); 254 | } 255 | 256 | goTo(step) { 257 | let item = this.allow_infinite_swipe 258 | ? step 259 | : Math.max(0, Math.min(step, this.elements_count - 1)); 260 | this.changeItem(item); 261 | } 262 | 263 | prevItem() { 264 | let step = this.active_indicator - 1; 265 | this.goTo(step); 266 | } 267 | 268 | nextItem() { 269 | let step = this.active_indicator + 1; 270 | this.goTo(step); 271 | } 272 | 273 | /** 274 | * Retrieves the current properties of the carousel. 275 | * @returns {Object} Carousel properties object. 276 | * @property {number} elements_count - The total number of carousel items. 277 | * @property {number} active_item - The index of the currently active carousel item. 278 | * @property {HTMLElement} active_element - The currently active carousel item element. 279 | */ 280 | 281 | getProps() { 282 | return { 283 | elements_count: this.elements_count, 284 | active_item: this.active_item, 285 | active_element: this.elements !== null ? this.elements[this.active_item] : null 286 | }; 287 | } 288 | } 289 | 290 | export default SwipeSnap; 291 | -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | export { default as Swipe } from "./components/Swipe.svelte"; 2 | export { default as SwipeItem } from "./components/SwipeItem.svelte"; 3 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | export { default as Swipe } from './components/Swipe.svelte'; 2 | export { default as SwipeItem } from './components/SwipeItem.svelte'; 3 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "checkJs": true, 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "resolveJsonModule": true, 8 | "skipLibCheck": true, 9 | "sourceMap": true, 10 | "strict": true 11 | } 12 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias and https://kit.svelte.dev/docs/configuration#files 13 | // 14 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 15 | // from the referenced tsconfig.json - TypeScript does not merge them in 16 | } 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-swipe", 3 | "version": "2.0.4", 4 | "description": "A Svelte component to swipe elements just like a snap", 5 | "homepage": "https://github.com/SharifClick/svelte-swipe", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/SharifClick/svelte-swipe" 9 | }, 10 | "publishConfig": { 11 | "registry": "https://registry.npmjs.org/" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/SharifClick/svelte-swipe/issues" 15 | }, 16 | "author": { 17 | "name": "Sharif Ahmed", 18 | "email": "me.sharifahmed@gmail.com", 19 | "url": "http://sharifahmed.me" 20 | }, 21 | "license": "MIT", 22 | "scripts": { 23 | "dev": "vite dev --force", 24 | "build": "vite build && npm run package", 25 | "package": "svelte-kit sync && svelte-package", 26 | "check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json", 27 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch", 28 | "lint": "prettier --plugin-search-dir . --check . && eslint .", 29 | "format": "prettier --plugin-search-dir . --write .", 30 | "deploy": "npm run build && touch build/.nojekyll && gh-pages -d build -t true" 31 | }, 32 | "devDependencies": { 33 | "@sveltejs/adapter-auto": "2.1.0", 34 | "@sveltejs/adapter-static": "^2.0.3", 35 | "@sveltejs/kit": "^1.24.1", 36 | "@sveltejs/package": "^2.2.2", 37 | "gh-pages": "^6.0.0", 38 | "eslint": "^8.48.0", 39 | "eslint-config-prettier": "^9.0.0", 40 | "eslint-plugin-svelte": "^2.33.0", 41 | "prettier": "^3.0.3", 42 | "prettier-plugin-svelte": "^3.0.3", 43 | "svelte": "^4.2.0", 44 | "svelte-check": "^3.5.1", 45 | "svelte-preprocess": "^5.0.4", 46 | "vite": "^4.4.9" 47 | }, 48 | "peerDependencies": { 49 | "svelte": "^3.54.0 || ^4.0.0" 50 | }, 51 | "exports": { 52 | ".": { 53 | "types": "./dist/index.d.ts", 54 | "svelte": "./dist/index.js" 55 | } 56 | }, 57 | "files": [ 58 | "dist" 59 | ], 60 | "main": "./dist/index.js", 61 | "type": "module", 62 | "keywords": [ 63 | "svelte", 64 | "svelte.js", 65 | "sveltejs", 66 | "svelte-swipe", 67 | "svelte-slider", 68 | "swipe", 69 | "slider", 70 | "carousel", 71 | "svelte-carousel" 72 | ] 73 | } 74 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | %sveltekit.head% 11 | 12 | 13 | 14 |
%sveltekit.body%
15 | 16 | 17 | -------------------------------------------------------------------------------- /src/lib/components/Swipe.svelte: -------------------------------------------------------------------------------- 1 | 159 | 160 |
161 |
162 |
163 | 164 |
165 |
166 | 167 | 194 | 195 | 247 | -------------------------------------------------------------------------------- /src/lib/components/SwipeItem.svelte: -------------------------------------------------------------------------------- 1 | 29 | 30 |
35 |
36 | 37 |
38 |
39 | 40 | 56 | -------------------------------------------------------------------------------- /src/lib/helpers/SwipeSnap.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | /** 3 | * A class for creating a swipeable carousel with snapping behavior. 4 | * @class 5 | */ 6 | 7 | class SwipeSnap { 8 | /** 9 | * Creates an instance of SwipeSnap. 10 | * @constructor 11 | * @param {Object} [options={}] - Options for configuring the SwipeSnap carousel. 12 | * @param {HTMLElement} [options.element] - The HTML element that contains the carousel. 13 | * @param {boolean} [options.is_vertical=false] - Whether the carousel is vertical (true) or horizontal (false). 14 | * @param {number} [options.transition_duration=300] - The duration of the transition animation in milliseconds. 15 | * @param {boolean} [options.allow_infinite_swipe=false] - Whether to allow infinite looping of carousel items. 16 | */ 17 | 18 | constructor(options = {}) { 19 | this.element = options.element; 20 | this.wrapper = this.element.querySelector('.swipeable-slot-wrapper'); 21 | this.handler = this.element.querySelector('.swipe-handler'); 22 | this.elements = this.wrapper.querySelectorAll('.swipeable-item'); 23 | this.elements_count = this.elements.length; 24 | 25 | this.is_vertical = options.is_vertical; 26 | this.transition_duration = options.transition_duration; 27 | this.allow_infinite_swipe = options.allow_infinite_swipe; 28 | 29 | this.pos_axis = 0; 30 | this.page_axis = options.is_vertical ? 'pageY' : 'pageX'; 31 | this.axis = 0; 32 | this.long_touch = false; 33 | this.last_axis_pos = 0; 34 | this.default_index = 0; 35 | this.active_indicator = 0; 36 | this.active_item = 0; 37 | this.touch_active = false; 38 | 39 | if (options.allow_infinite_swipe) { 40 | this.setInfiniteSwipe(); 41 | } 42 | 43 | this.SWIPE = this.swipe.bind(this); 44 | this.SWIPE_START = this.swipeStart.bind(this); 45 | this.SWIPE_END = this.swipeEnd.bind(this); 46 | 47 | this.handler.addEventListener('touchstart', this.SWIPE_START); 48 | this.handler.addEventListener('mousedown', this.SWIPE_START); 49 | } 50 | 51 | /** 52 | * Prevents the default behavior of an event. 53 | * @param {Event} event - The event to prevent. 54 | */ 55 | 56 | prevent(event) { 57 | if (event && event.cancelable) { 58 | event.preventDefault(); 59 | } 60 | event && event.stopImmediatePropagation(); 61 | event && event.stopPropagation(); 62 | } 63 | 64 | update() { 65 | let { offsetWidth, offsetHeight } = this.wrapper; 66 | this.available_space = this.is_vertical ? offsetHeight : offsetWidth; 67 | 68 | this.setElementsPosition({ 69 | init: true, 70 | elems: [...this.elements], 71 | available_space: this.available_space, 72 | has_infinite_loop: this.allow_infinite_swipe 73 | }); 74 | 75 | this.available_distance = 0; 76 | this.available_measure = this.available_space * (this.elements_count - 1); 77 | if (this.default_index) { 78 | this.changeItem(this.default_index); 79 | } 80 | } 81 | 82 | setInfiniteSwipe() { 83 | this.wrapper.prepend(this.elements[this.elements_count - 1].cloneNode(true)); 84 | this.wrapper.append(this.elements[0].cloneNode(true)); 85 | this.elements = this.element.querySelectorAll('.swipeable-item'); 86 | } 87 | 88 | setElementsPosition({ 89 | elems = [], 90 | available_space = 0, 91 | pos_axis = 0, 92 | has_infinite_loop = false, 93 | distance = 0, 94 | moving = false, 95 | init = false, 96 | end = false, 97 | reset = false 98 | }) { 99 | elems.forEach((element, i) => { 100 | let idx = has_infinite_loop ? i - 1 : i; 101 | if (init) { 102 | element.style.transform = this.generateTranslateValue(available_space * idx); 103 | element.classList.remove('is-item-hidden'); 104 | } 105 | if (moving) { 106 | element.style.cssText = this.generateTouchPosCss(available_space * idx - distance); 107 | } 108 | if (end) { 109 | element.style.cssText = this.generateTouchPosCss(available_space * idx - pos_axis, true); 110 | } 111 | if (reset) { 112 | element.style.cssText = this.generateTouchPosCss(available_space * idx - pos_axis); 113 | } 114 | }); 115 | } 116 | 117 | generateTranslateValue(value) { 118 | return this.is_vertical ? `translate3d(0, ${value}px, 0)` : `translate3d(${value}px, 0, 0)`; 119 | } 120 | 121 | generateTouchPosCss(value, touch_end = false) { 122 | let transform_string = this.generateTranslateValue(value); 123 | let _css = ` 124 | -webkit-transition-duration: ${touch_end ? this.transition_duration : '0'}ms; 125 | transition-duration: ${touch_end ? this.transition_duration : '0'}ms; 126 | -webkit-transform: ${transform_string}; 127 | -ms-transform: ${transform_string};`; 128 | return _css; 129 | } 130 | 131 | swipeStart(event) { 132 | this.prevent(event); 133 | this.touch_active = true; 134 | this.long_touch = false; 135 | setTimeout(() => { 136 | this.long_touch = true; 137 | }, 250); 138 | this.axis = event.touches ? event.touches[0][this.page_axis] : event[this.page_axis]; 139 | this.eventDelegate('add'); 140 | } 141 | 142 | swipe(event) { 143 | if (this.touch_active) { 144 | this.prevent(event); 145 | let axis = event.touches ? event.touches[0][this.page_axis] : event[this.page_axis]; 146 | let distance = this.axis - axis + this.pos_axis; 147 | if (!this.allow_infinite_swipe) { 148 | if ( 149 | (this.pos_axis == 0 && this.axis < axis) || 150 | (this.pos_axis == this.available_measure && this.axis > axis) 151 | ) { 152 | return; 153 | } 154 | } 155 | event.preventDefault(); 156 | 157 | // if (distance <= availableMeasure && distance >= 0) { 158 | // } 159 | this.setElementsPosition({ 160 | moving: true, 161 | elems: [...this.elements], 162 | available_space: this.available_space, 163 | has_infinite_loop: this.allow_infinite_swipe, 164 | distance 165 | }); 166 | this.available_distance = distance; 167 | this.last_axis_pos = axis; 168 | } 169 | } 170 | 171 | swipeEnd(event) { 172 | this.prevent(event); 173 | let direction = this.axis < this.last_axis_pos; 174 | this.touch_active = false; 175 | let available_space = this.available_space; 176 | let accidental_touch = 177 | Math.round(this.available_space / 50) > Math.abs(this.axis - this.last_axis_pos); 178 | if (this.long_touch || accidental_touch) { 179 | this.available_distance = 180 | Math.round(this.available_distance / available_space) * available_space; 181 | } else { 182 | this.available_distance = direction 183 | ? Math.floor(this.available_distance / available_space) * available_space 184 | : Math.ceil(this.available_distance / available_space) * available_space; 185 | } 186 | this.axis = null; 187 | this.last_axis_pos = null; 188 | this.pos_axis = this.available_distance; 189 | this.active_indicator = this.available_distance / available_space; 190 | this.active_item = this.active_indicator; 191 | this.default_index = this.active_item; 192 | 193 | this.setElementsPosition({ 194 | end: true, 195 | elems: [...this.elements], 196 | available_space: available_space, 197 | pos_axis: this.pos_axis, 198 | has_infinite_loop: this.allow_infinite_swipe 199 | }); 200 | 201 | if (this.allow_infinite_swipe) { 202 | if (this.active_item === -1) { 203 | this.pos_axis = available_space * (this.elements_count - 1); 204 | } 205 | if (this.active_item === this.elements_count) { 206 | this.pos_axis = 0; 207 | } 208 | this.active_indicator = this.pos_axis / available_space; 209 | this.active_item = this.active_indicator; 210 | this.default_index = this.active_item; 211 | 212 | setTimeout(() => { 213 | this.setElementsPosition({ 214 | reset: true, 215 | elems: [...this.elements], 216 | available_space: available_space, 217 | pos_axis: this.pos_axis, 218 | has_infinite_loop: this.allow_infinite_swipe 219 | }); 220 | }, this.transition_duration); 221 | } 222 | let swipe_direction = direction ? 'right' : 'left'; 223 | this.eventDelegate('remove'); 224 | 225 | const customEvent = new CustomEvent('swipe_end', { 226 | detail: { 227 | active_item: this.active_item, 228 | swipe_direction, 229 | active_element: this.elements[this.active_item] 230 | } 231 | }); 232 | 233 | this.element.dispatchEvent(customEvent); 234 | } 235 | 236 | eventDelegate(type) { 237 | let delegationTypes = { 238 | add: 'addEventListener', 239 | remove: 'removeEventListener' 240 | }; 241 | if (typeof window !== 'undefined') { 242 | window[delegationTypes[type]]('mousemove', this.SWIPE); 243 | window[delegationTypes[type]]('mouseup', this.SWIPE_END); 244 | window[delegationTypes[type]]('touchmove', this.SWIPE, { passive: false }); 245 | window[delegationTypes[type]]('touchend', this.SWIPE_END, { passive: false }); 246 | } 247 | } 248 | 249 | changeItem(item) { 250 | let max = this.available_space; 251 | this.available_distance = max * item; 252 | this.active_indicator = item; 253 | this.swipeEnd(); 254 | } 255 | 256 | goTo(step) { 257 | let item = this.allow_infinite_swipe 258 | ? step 259 | : Math.max(0, Math.min(step, this.elements_count - 1)); 260 | this.changeItem(item); 261 | } 262 | 263 | prevItem() { 264 | let step = this.active_indicator - 1; 265 | this.goTo(step); 266 | } 267 | 268 | nextItem() { 269 | let step = this.active_indicator + 1; 270 | this.goTo(step); 271 | } 272 | 273 | /** 274 | * Retrieves the current properties of the carousel. 275 | * @returns {Object} Carousel properties object. 276 | * @property {number} elements_count - The total number of carousel items. 277 | * @property {number} active_item - The index of the currently active carousel item. 278 | * @property {HTMLElement} active_element - The currently active carousel item element. 279 | */ 280 | 281 | getProps() { 282 | return { 283 | elements_count: this.elements_count, 284 | active_item: this.active_item, 285 | active_element: this.elements !== null ? this.elements[this.active_item] : null 286 | }; 287 | } 288 | } 289 | 290 | export default SwipeSnap; 291 | -------------------------------------------------------------------------------- /src/lib/index.js: -------------------------------------------------------------------------------- 1 | export { default as Swipe } from './components/Swipe.svelte'; 2 | export { default as SwipeItem } from './components/SwipeItem.svelte'; 3 | -------------------------------------------------------------------------------- /src/routes/+layout.js: -------------------------------------------------------------------------------- 1 | export const prerender = true; 2 | // 3 | -------------------------------------------------------------------------------- /src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 118 | 119 | 124 | 154 | 155 |
156 |
157 |
158 |

Svelte Swipe

159 |

Swipable items wrapper component for Svelte

160 |
161 |
162 | 163 |
164 |
165 |
166 |
167 |
168 | 173 | 174 |
175 |
176 | 177 | 178 |
179 |
180 |
181 |
182 |
183 |
184 | 185 | {#each images as image} 186 | 187 | 188 | 189 | {/each} 190 | 191 |
192 |
193 |
194 |
195 |
196 | 202 |
203 | {#if customThumbnail} 204 |
205 |
206 | {#each images as image, i} 207 | changeSlide(i)} 210 | style="height:30px; width:30px; cursor:pointer" 211 | src={base + '/' + image} 212 | alt="" 213 | /> 214 | {/each} 215 |
216 |
217 | {/if} 218 |
219 |
220 | 223 | 226 |
227 |
228 |
229 |
230 |
231 | 232 |
233 |
234 |
235 |

Allow pointer events inside Swipe Item

236 |
237 | 238 | 239 |
240 | 241 |
242 |
243 | 244 | 245 |
246 | 247 |
248 |
249 | 250 | 251 |
252 | 253 |
254 |
255 | 256 | 257 |
258 | 259 |
260 |
261 |
262 |
263 |
264 |
265 | 266 |
267 |
268 |
269 |

Dynamic Height

270 |
271 |

Dynamic height with children

272 |
273 |

Item Height: {swipe_holder_height}

274 |
275 |
276 |
277 | 278 | {#each dy_images as image, i} 279 | 284 |
285 | 286 |
287 |
288 | {/each} 289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |

Infinite swipe

297 |
298 |
299 |
300 | 305 | 306 |
307 |
308 | 309 | 310 |
311 |
312 |
313 |
314 |
315 |
316 | 324 | {#each images as image} 325 | 326 | 327 | 328 | {/each} 329 | 330 |
331 |
332 |
333 |
334 |
335 | 341 |
342 | {#if customThumbnail} 343 |
344 |
345 | {#each images as image, i} 346 | changeSlideInf(i)} 349 | style="height:30px; width:30px; cursor:pointer" 350 | src={base + '/' + image} 351 | alt="" 352 | /> 353 | {/each} 354 |
355 |
356 | {/if} 357 |
358 |
359 | 362 | 365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |

Unsplash Images

374 |
375 |

Unsplash Images

376 |
377 |

Item Height: {swipe_holder_height_unplash}

378 |
379 |
380 |
381 | 382 | {#each up_images as image, i} 383 | 384 |
385 | 386 |
387 |
388 | {/each} 389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |

Vertical Swipe 🔥

397 |
398 | 399 | {#each images as image} 400 | 401 | 402 | 403 | {/each} 404 | 405 |
406 |
407 |
408 |
409 | 410 | 475 | -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SharifClick/svelte-swipe/7a771f0a0724452116eca1600817cbe611d15a62/static/favicon.png -------------------------------------------------------------------------------- /static/images/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SharifClick/svelte-swipe/7a771f0a0724452116eca1600817cbe611d15a62/static/images/1.jpg -------------------------------------------------------------------------------- /static/images/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SharifClick/svelte-swipe/7a771f0a0724452116eca1600817cbe611d15a62/static/images/2.jpg -------------------------------------------------------------------------------- /static/images/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SharifClick/svelte-swipe/7a771f0a0724452116eca1600817cbe611d15a62/static/images/3.jpg -------------------------------------------------------------------------------- /static/images/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SharifClick/svelte-swipe/7a771f0a0724452116eca1600817cbe611d15a62/static/images/4.jpg -------------------------------------------------------------------------------- /static/images/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SharifClick/svelte-swipe/7a771f0a0724452116eca1600817cbe611d15a62/static/images/5.jpg -------------------------------------------------------------------------------- /static/images/dy-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SharifClick/svelte-swipe/7a771f0a0724452116eca1600817cbe611d15a62/static/images/dy-1.jpg -------------------------------------------------------------------------------- /static/images/dy-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SharifClick/svelte-swipe/7a771f0a0724452116eca1600817cbe611d15a62/static/images/dy-2.jpg -------------------------------------------------------------------------------- /static/images/dy-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SharifClick/svelte-swipe/7a771f0a0724452116eca1600817cbe611d15a62/static/images/dy-3.jpg -------------------------------------------------------------------------------- /static/images/dy-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SharifClick/svelte-swipe/7a771f0a0724452116eca1600817cbe611d15a62/static/images/dy-4.jpg -------------------------------------------------------------------------------- /static/images/dy-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SharifClick/svelte-swipe/7a771f0a0724452116eca1600817cbe611d15a62/static/images/dy-5.jpg -------------------------------------------------------------------------------- /static/images/url-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SharifClick/svelte-swipe/7a771f0a0724452116eca1600817cbe611d15a62/static/images/url-code.png -------------------------------------------------------------------------------- /svelte-swipe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SharifClick/svelte-swipe/7a771f0a0724452116eca1600817cbe611d15a62/svelte-swipe.png -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-static'; 2 | 3 | /** @type {import('@sveltejs/kit').Config} */ 4 | const config = { 5 | kit: { 6 | paths: { 7 | base: '/svelte-swipe' 8 | }, 9 | adapter: adapter() 10 | } 11 | }; 12 | 13 | export default config; 14 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { sveltekit } from "@sveltejs/kit/vite"; 3 | 4 | /** @type {import('vite').UserConfig} */ 5 | const config = { 6 | plugins: [sveltekit()], 7 | server: { 8 | port: 5000, 9 | }, 10 | 11 | preview: { 12 | port: 5000, 13 | }, 14 | }; 15 | 16 | export default config; 17 | 18 | --------------------------------------------------------------------------------