├── .github └── workflows │ └── static.yml ├── .gitignore ├── CNAME ├── LICENSE ├── README.md ├── index.html ├── itemslide.js └── package.json /.github/workflows/static.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy static content to Pages 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["master"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 20 | concurrency: 21 | group: "pages" 22 | cancel-in-progress: false 23 | 24 | jobs: 25 | # Single deploy job since we're just deploying 26 | deploy: 27 | environment: 28 | name: github-pages 29 | url: ${{ steps.deployment.outputs.page_url }} 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v4 34 | - name: Setup Pages 35 | uses: actions/configure-pages@v5 36 | - name: Upload artifact 37 | uses: actions/upload-pages-artifact@v3 38 | with: 39 | # Upload entire repository 40 | path: '.' 41 | - name: Deploy to GitHub Pages 42 | id: deployment 43 | uses: actions/deploy-pages@v4 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | tags 3 | dist 4 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | itemslide.org 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024 Nir Lichtman 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 SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ItemSlide 2 | 3 | A simple & beautiful vanilla JavaScript touch carousel 4 | 5 | ### Features 6 | - Touch swiping 7 | - Mousewheel scrolling 8 | - The ability to "swipe out" slides 9 | - Centered carousel or left sided (default is centered) 10 | 11 | ## Documentation 12 | 13 | ### Getting Started 14 | 15 | #### Markup 16 | ```html 17 |
18 | 22 |
23 | ``` 24 | 25 | #### CSS 26 | 27 | For this example CSS, we assume the carousel is contained within an element that has the id "scrolling" 28 | 29 | ```css 30 | #scrolling { 31 | overflow: hidden; 32 | } 33 | 34 | #scrolling ul { 35 | margin: 0; 36 | padding: 0; 37 | list-style-type: none; 38 | position: absolute; 39 | transform-style: preserve; 40 | } 41 | 42 | #scrolling ul li { 43 | float: left; 44 | } 45 | ``` 46 | 47 | #### Include Script 48 | 49 | ```html 50 | 51 | ``` 52 | 53 | #### Initialize 54 | 55 | ```js 56 | var itemslide; 57 | 58 | window.addEventListener("load", () => { 59 | var element = document.querySelector("#scrolling ul"); 60 | itemslide = new Itemslide(element, {}); 61 | }); 62 | ``` 63 | 64 | ### Options 65 | 66 | Options are passed as key-values into the object that the ```Itemslide``` constructor gets into the second parameter, for example: 67 | 68 | ```js 69 | new Itemslide(element, { duration: 100 }); 70 | ``` 71 | 72 | Will initialize Itemslide with a custom duration of 100ms. 73 | 74 | Here are the available options: 75 | 76 | - ```duration``` - duration of slide animation {default: 350ms} 77 | - ```swipeSensitivity``` - swiping sensitivity {default: 150} 78 | - ```disableSlide``` - disable swiping and panning {default: false} 79 | - ```disableClickToSlide``` - disable click to slide {default: false} 80 | - ```disableAutoWidth``` - disable the automatic calculation of width (so that you could do it manually) {default: false} 81 | - ```disableScroll``` - disable the sliding triggered by mousewheel scrolling {default: false} 82 | - ```start``` - index of slide that appears when initializing {default: 0} 83 | - ```panThreshold``` - can be also considered as panning sensitivity {default: 0.3}(precentage of slide width) 84 | - ```oneItem``` - set this to true if the navigation is full screen or one slide every time. {default: false} 85 | - ```parentWidth``` - set this to true if you want the width of the slides to be the width of the parent of ul. {default: false} 86 | - ```swipeOut``` - set this to true to enable the swipe out feature. {default: false} ( 87 | - ```leftSided``` - left sided carousel (instead of default force-centered) {default: false} 88 | 89 | ### Methods 90 | 91 | - ```getActiveIndex()``` - get current active slide index 92 | - ```getCurrentPos()``` - get current position of carousel (pixels) 93 | - ```nextSlide()``` - goes to next slide 94 | - ```previousSlide()``` - goes to previous slide 95 | - ```gotoSlide(i)``` - goes to a specific slide by index 96 | - ```reload()``` - recalculates width and center object (recommended to call when resize occures) 97 | - ```addSlide(data)``` - adds in the end of the carousel a new item. 98 | - ```removeSlide(index)``` - removes a specific slide by index. 99 | 100 | > NOTE: addSlide automatically adds li tags. 101 | 102 | ### Events 103 | 104 | ItemSlide triggers the following events on the element it is initialized on: 105 | 106 | - ```carouselChangePos``` - triggered when the position of the carousel is changed 107 | - ```carouselPan``` - triggered when user pans 108 | - ```carouselChangeActiveIndex``` - triggered when the current active item has changed 109 | - ```carouselSwipeOut``` - triggered when user swipes out a slide (if swipeOut is enabled) 110 | * ```event.slideIndex``` - get index of swiped out slide 111 | - ```carouselClickSlide``` - triggered when clicking/tapping a slide 112 | * ```event.slideIndex``` - get index of the clicked slide 113 | 114 | ### Classes 115 | 116 | The current active slide gets the 'itemslide-active' class. 117 | 118 | ### Extras 119 | 120 | - attribute 'no-drag'- If you want to disable dragging only on a certain element in the carousel just add this attribute to the element. (example: ```
  • ```) 121 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ItemSlide.js - JavaScript touch carousel 9 | 100 | 101 | 114 | 115 | 116 | 121 |
    122 |
    123 | JavaScript Carousel Library 124 |
    125 |
    126 | 127 | Source on Github 128 | 129 |
    130 |
    131 |
      132 |
    • 133 | 1 134 |
    • 135 |
    • 136 | 2 137 |
    • 138 |
    • 139 | 3 140 |
    • 141 |
    • 142 | 4 143 |
    • 144 |
    145 |
    146 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /itemslide.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | (function () 4 | { 5 | var carousel, totalDuration, totalBack, currentPos, startTime; 6 | 7 | function gotoSlideByIndex(i, withoutAnimation) 8 | { 9 | var isBoundary; 10 | 11 | if (i >= carousel.element.children.length - 1 || i <= 0) { 12 | isBoundary = true; 13 | i = Math.min(Math.max(i, 0), carousel.element.children.length - 1); 14 | } 15 | else { 16 | isBoundary = false; 17 | } 18 | 19 | changeActiveSlideTo(i); 20 | 21 | totalDuration = Math.max(carousel.options.duration - ((1920 / window.outerWidth) * Math.abs(carousel.vars.velocity) * 9 * (carousel.options.duration / 230)) - (isOutBoundaries() ? (carousel.vars.distanceFromStart / 15) : 0) * (carousel.options.duration / 230), 50); 22 | 23 | totalBack = isBoundary ? ((Math.abs(carousel.vars.velocity) * 250) / window.outerWidth) : 0; 24 | currentPos = getTranslate3d(carousel.element).x; 25 | carousel.currentLandPos = getPositionByIndex(i); 26 | 27 | if (withoutAnimation) { 28 | setTranslate3d(carousel.element, getPositionByIndex(i)); 29 | return; 30 | } 31 | 32 | window.cancelAnimationFrame(carousel.slidesGlobalID); 33 | 34 | startTime = Date.now(); 35 | carousel.slidesGlobalID = window.requestAnimationFrame(animationRepeat); 36 | } 37 | 38 | function getLandingSlideIndex(x) 39 | { 40 | for (var i = 0; i < carousel.element.children.length; i++) { 41 | if (carousel.getSlidesWidth(false, i) + carousel.element.children[i].offsetWidth / 2 - 42 | carousel.element.children[i].offsetWidth * carousel.options.panThreshold * carousel.vars.direction - getPositionByIndex(0) > x) { 43 | 44 | if (!carousel.options.oneItem) { 45 | return i; 46 | } 47 | 48 | if (i != carousel.vars.currentIndex) { 49 | return carousel.vars.currentIndex + carousel.vars.direction; 50 | } else { 51 | return carousel.vars.currentIndex; 52 | } 53 | } 54 | } 55 | 56 | return carousel.options.oneItem ? carousel.vars.currentIndex + 1 : carousel.element.children.length - 1; 57 | } 58 | 59 | function isOutBoundaries() 60 | { 61 | return (Math.floor(getTranslate3d(carousel.element).x) > (getPositionByIndex(0)) && carousel.vars.direction == -1) || (Math.ceil(getTranslate3d(carousel.element).x) < (getPositionByIndex(carousel.element.children.length - 1)) && carousel.vars.direction == 1); 62 | } 63 | 64 | function changeActiveSlideTo(i) 65 | { 66 | var oldSlide = carousel.element.children[carousel.vars.currentIndex || 0]; 67 | oldSlide.classList.remove("itemslide-active"); 68 | 69 | carousel.element.children[i || 0].classList.add("itemslide-active"); 70 | 71 | if (i != carousel.options.currentIndex) { 72 | carousel.vars.currentIndex = i; 73 | carousel.element.dispatchEvent(new Event("carouselChangeActiveIndex")); 74 | } 75 | } 76 | 77 | function getPositionByIndex(i) 78 | { 79 | var slidesWidth = carousel.getSlidesWidth(false, i); 80 | var containerMinusSlideWidth = carousel.element.parentElement.offsetWidth - carousel.element.children[i].offsetWidth; 81 | return -(slidesWidth - (containerMinusSlideWidth / (carousel.options.leftSided ? 1 : 2))); 82 | } 83 | 84 | function animationRepeat() 85 | { 86 | var currentTime = Date.now() - startTime; 87 | 88 | if (carousel.options.leftSided) { 89 | carousel.currentLandPos = clamp(-(carousel.vars.allSlidesWidth - carousel.element.parentElement.clientWidth), 0, carousel.currentLandPos); 90 | } 91 | 92 | carousel.element.dispatchEvent(new Event("carouselChangePos")); 93 | 94 | var x = currentPos - easeOutBack(currentTime, 0, currentPos - carousel.currentLandPos, totalDuration, totalBack); 95 | setTranslate3d(carousel.element, x); 96 | 97 | if (currentTime >= totalDuration) { 98 | setTranslate3d(carousel.element, carousel.currentLandPos); 99 | return; 100 | } 101 | 102 | carousel.slidesGlobalID = requestAnimationFrame(animationRepeat); 103 | } 104 | 105 | function easeOutBack(t, b, c, d, elasticity) 106 | { 107 | if (elasticity == undefined) { 108 | elasticity = 1.70158; 109 | } 110 | 111 | return c * ((t = t / d - 1) * t * ((elasticity + 1) * t + elasticity) + 1) + b; 112 | } 113 | 114 | function getTranslate3d(element) 115 | { 116 | var transform = element.style.transform; 117 | 118 | var vals = transform.replace("translate3d", "").replace("(", "").replace(")", "").replace(" ", "").replace("px", "").split(","); 119 | 120 | return { 121 | x: parseFloat(vals[0]), 122 | y: parseFloat(vals[1]) 123 | }; 124 | } 125 | 126 | function setTranslate3d(element, x, y) 127 | { 128 | element.style.transform = "translate3d(" + x + "px," + (y || 0) + "px, 0px)"; 129 | } 130 | 131 | function clamp(min, max, value) 132 | { 133 | return Math.min(Math.max(value, min), max); 134 | } 135 | 136 | function getCurrentTotalWidth(inSlides) 137 | { 138 | var width = 0; 139 | 140 | Array.from(inSlides.children).forEach((slide) => { 141 | width += slide.offsetWidth; 142 | }); 143 | 144 | return width; 145 | } 146 | 147 | function slideout() 148 | { 149 | var slides = carousel.element; 150 | var settings = carousel.options; 151 | var vars = carousel.vars; 152 | 153 | var swipeOutLandPos = -400, 154 | swipeOutStartTime = Date.now(), 155 | currentSwipeOutPos = 0, 156 | swipeOutGlobalID = 0; 157 | 158 | var durationSave = 0, 159 | savedOpacity = 1, 160 | prev; 161 | 162 | var isSwipeDirectionUp; 163 | 164 | carousel.element.endAnimation = true; 165 | carousel.element.savedSlideIndex = 0; 166 | 167 | var goback = false; 168 | 169 | carousel.swipeOut = function () { 170 | currentSwipeOutPos = getTranslate3d(document.querySelector(".itemslide_slideoutwrap")).y; 171 | 172 | isSwipeDirectionUp = currentSwipeOutPos < 0; 173 | 174 | if (!isSwipeDirectionUp) { 175 | swipeOutLandPos = 400; 176 | } else { 177 | swipeOutLandPos = -400; 178 | } 179 | 180 | if (Math.abs(0 - currentSwipeOutPos) < 50) { 181 | goback = true; 182 | swipeOutLandPos = 0; 183 | } else { 184 | goback = false; 185 | 186 | var swipeOutEvent = new Event("carouselSwipeOut"); 187 | swipeOutEvent.slideIndex = carousel.element.savedSlideIndex; 188 | carousel.element.dispatchEvent(swipeOutEvent); 189 | } 190 | 191 | removeWrapper = 0; 192 | durationSave = settings.duration; 193 | prev = carousel.element.savedSlide; 194 | swipeOutStartTime = Date.now(); 195 | savedOpacity = carousel.element.savedSlide.style.opacity || 1; 196 | 197 | if (carousel.element.savedSlideIndex < carousel.vars.currentIndex) { 198 | before = true; 199 | 200 | var toWrap = carousel.element.querySelectorAll("ul > li:nth-child(-n+" + (carousel.element.savedSlideIndex + 1) + ")"); 201 | 202 | if (toWrap.length > 0) { 203 | wrapElements(toWrap, "itemslide_move"); 204 | } 205 | } else { 206 | before = false; 207 | 208 | var toWrap = carousel.element.querySelectorAll("ul > li:nth-child(n+" + (carousel.element.savedSlideIndex + 2) + ")"); 209 | 210 | if (toWrap.length > 0) { 211 | wrapElements(toWrap, "itemslide_move"); 212 | } 213 | } 214 | 215 | enableOpacity = true; 216 | carousel.element.endAnimation = false; 217 | swipeOutGlobalID = requestAnimationFrame(swipeOutAnimation); 218 | }; 219 | 220 | var enableOpacity = true, 221 | currentTime = 0; 222 | 223 | var removeWrapper = 0; 224 | 225 | var before = false; 226 | var itemslideMove = ".itemslide_move"; 227 | 228 | function swipeOutAnimation() { 229 | currentTime = Date.now() - swipeOutStartTime; 230 | 231 | if (enableOpacity) { 232 | setTranslate3d(document.querySelector(".itemslide_slideoutwrap"), 0, currentSwipeOutPos - easeOutBack(currentTime, 0, currentSwipeOutPos - swipeOutLandPos, 250, 0)); 233 | carousel.element.savedSlide.style.opacity = savedOpacity - easeOutBack(currentTime, 0, savedOpacity, 250, 0) * (goback ? -1 : 1); 234 | } else { 235 | var itemslideMoveElement = document.querySelector(itemslideMove); 236 | 237 | if (goback) 238 | { 239 | unwrapElements(document.querySelector(".itemslide_slideoutwrap").children); 240 | if (itemslideMoveElement) { 241 | unwrapElements(itemslideMoveElement.children); 242 | } 243 | 244 | carousel.element.endAnimation = true; 245 | currentTime = 0; 246 | 247 | return; 248 | } 249 | 250 | if (itemslideMoveElement) { 251 | setTranslate3d(itemslideMoveElement, 0 - easeOutBack(currentTime - 250, 0, 0 + carousel.element.savedSlide.offsetWidth, 125, 0) * (before ? (-1) : 1), 0); 252 | } 253 | } 254 | 255 | if (removeWrapper == 1) { 256 | 257 | unwrapElements(document.querySelector(".itemslide_slideoutwrap").children); 258 | 259 | if (carousel.element.savedSlideIndex == carousel.vars.currentIndex) { 260 | var firstMoveSlide = document.querySelector(itemslideMove + ' :nth-child(1)'); 261 | if (firstMoveSlide) { 262 | firstMoveSlide.classList.add("itemslide-active"); 263 | } 264 | } 265 | 266 | if (carousel.element.savedSlideIndex == (carousel.element.children.length - 1) && !before && carousel.element.savedSlideIndex == carousel.vars.currentIndex) 267 | { 268 | settings.duration = 200; 269 | gotoSlideByIndex(carousel.element.children.length - 2); 270 | 271 | } 272 | 273 | if (carousel.element.savedSlideIndex == 0 && carousel.vars.currentIndex != 0) { 274 | currentTime = 500; 275 | } 276 | 277 | removeWrapper = -1; 278 | } 279 | 280 | if (currentTime >= 250) { 281 | enableOpacity = false; 282 | 283 | if (removeWrapper != -1) { 284 | removeWrapper = 1; 285 | } 286 | 287 | if (currentTime >= 375) { 288 | if (document.querySelector(itemslideMove)) { 289 | unwrapElements(document.querySelector(itemslideMove).children); 290 | } 291 | 292 | var shouldGotoAfterRemoveSlide = false; 293 | 294 | if ((carousel.element.savedSlideIndex == 0 && carousel.vars.currentIndex != 0) || (before && carousel.vars.currentIndex != carousel.element.children.length - 1)) { 295 | shouldGotoAfterRemoveSlide = true; 296 | } 297 | 298 | carousel.vars.instance.removeSlide(Array.from(prev.parentElement.children).indexOf(prev)); 299 | 300 | if (shouldGotoAfterRemoveSlide) { 301 | gotoSlideByIndex(carousel.vars.currentIndex - 1, true); 302 | } 303 | 304 | settings.duration = durationSave; 305 | currentTime = 0; 306 | carousel.element.endAnimation = true; 307 | 308 | return; 309 | } 310 | } 311 | 312 | swipeOutGlobalID = requestAnimationFrame(swipeOutAnimation); 313 | } 314 | } 315 | 316 | function wrapElements(elements, wrapperClassName) 317 | { 318 | elements = Array.from(elements); 319 | 320 | var wrapperElement = document.createElement("div"); 321 | wrapperElement.className = wrapperClassName; 322 | 323 | var parentElement = elements[0].parentElement; 324 | 325 | parentElement.insertBefore(wrapperElement, elements[0]); 326 | 327 | for (var element of elements) { 328 | var elementToWrap = parentElement.removeChild(element); 329 | 330 | wrapperElement.appendChild(elementToWrap); 331 | } 332 | } 333 | 334 | function unwrapElements(elements) 335 | { 336 | elements = Array.from(elements); 337 | 338 | var wrapper = elements[0].parentElement; 339 | var wrapperNextSibling = wrapper.nextSibling; 340 | 341 | var originalParent = wrapper.parentElement; 342 | 343 | originalParent.removeChild(wrapper); 344 | 345 | for (var element of elements) { 346 | if (wrapperNextSibling) { 347 | originalParent.insertBefore(element, wrapperNextSibling); 348 | } else { 349 | originalParent.appendChild(element); 350 | } 351 | } 352 | } 353 | 354 | function createEvents() 355 | { 356 | Array.from(carousel.element.children).forEach((slide) => { 357 | for (var eventType of ["mousedown", "touchstart"]) { 358 | slide.addEventListener(eventType, (e) => { 359 | touchstart.call(this, e); 360 | }); 361 | } 362 | }); 363 | 364 | for (var eventType of ["mouseup", "touchend"]) { 365 | window.addEventListener(eventType, (e) => { 366 | touchend(e); 367 | }); 368 | } 369 | } 370 | 371 | var swipeStartTime, isDown, startPreventDefault, startPointX, startPointY, verticalPan = false, 372 | horizontalPan; 373 | 374 | var verticalSlideFirstTimeCount; 375 | 376 | function getVerticalPan() 377 | { 378 | return verticalPan 379 | } 380 | 381 | function touchstart(e) 382 | { 383 | if (e.target.getAttribute("no-drag") === "true" || !carousel.element.endAnimation) { 384 | return; 385 | } 386 | 387 | var touch; 388 | 389 | if (e.type == 'touchstart') { 390 | touch = getTouch(e); 391 | } else { 392 | touch = e; 393 | } 394 | 395 | swipeStartTime = Date.now(); 396 | 397 | isDown = 1; 398 | 399 | startPreventDefault = 0; 400 | 401 | startPointX = touch.pageX; 402 | startPointY = touch.pageY; 403 | 404 | verticalPan = false; 405 | horizontalPan = false; 406 | 407 | carousel.element.savedSlide = e.target; 408 | 409 | carousel.element.savedSlideIndex = Array.from(carousel.element.savedSlide.parentElement.children).indexOf(carousel.element.savedSlide); 410 | 411 | verticalSlideFirstTimeCount = 0; 412 | 413 | window.addEventListener('mousemove', mousemove, { passive: false }); 414 | window.addEventListener('touchmove', mousemove, { passive: false }); 415 | 416 | window.getSelection().removeAllRanges(); 417 | } 418 | 419 | var savedStartPt, firstTime; 420 | 421 | function mousemove(e) 422 | { 423 | var touch; 424 | 425 | if (e.type == 'touchmove') { 426 | touch = getTouch(e); 427 | 428 | if (Math.abs(touch.pageX - startPointX) > 10) { 429 | startPreventDefault = 1; 430 | } 431 | 432 | if (startPreventDefault) { 433 | e.preventDefault(); 434 | } 435 | } 436 | else { 437 | touch = e; 438 | 439 | if (!carousel.options.disableSlide && !carousel.options.swipeOut) { 440 | e.preventDefault(); 441 | } 442 | } 443 | 444 | if ((-(touch.pageX - startPointX)) > 0) { 445 | carousel.vars.direction = 1; 446 | } else { 447 | carousel.vars.direction = -1; 448 | } 449 | 450 | if (isOutBoundaries()) { 451 | if (firstTime) { 452 | savedStartPt = touch.pageX; 453 | firstTime = 0; 454 | } 455 | } else { 456 | if (!firstTime) { 457 | carousel.currentLandPos = getTranslate3d(carousel.element).x; 458 | startPointX = touch.pageX; 459 | } 460 | 461 | firstTime = 1; 462 | } 463 | 464 | if (verticalSlideFirstTimeCount == 1) 465 | { 466 | Array.from(carousel.element.children).forEach((slide) => { 467 | slide.style.height = carousel.vars.slideHeight + "px" 468 | }); 469 | 470 | wrapElements([carousel.element.savedSlide], "itemslide_slideoutwrap", true); 471 | 472 | verticalSlideFirstTimeCount = -1; 473 | } 474 | 475 | if (Math.abs(touch.pageX - startPointX) > 6) 476 | { 477 | if (!verticalPan && carousel.element.endAnimation) { 478 | horizontalPan = true; 479 | } 480 | 481 | window.cancelAnimationFrame(carousel.slidesGlobalID); 482 | } 483 | 484 | if (Math.abs(touch.pageY - startPointY) > 6) { 485 | if (!horizontalPan && carousel.element.endAnimation) { 486 | verticalPan = true; 487 | } 488 | } 489 | 490 | if (horizontalPan) { 491 | 492 | if (carousel.options.disableSlide) { 493 | return; 494 | } 495 | 496 | if (carousel.options.leftSided) { 497 | carousel.currentLandPos = clamp(-(carousel.vars.allSlidesWidth - carousel.element.parentElement.clientWidth), 0, carousel.currentLandPos); 498 | } 499 | 500 | verticalPan = false; 501 | 502 | setTranslate3d(carousel.element, 503 | ((firstTime == 0) ? (savedStartPt - startPointX + (touch.pageX - savedStartPt) / 4) : (touch.pageX - startPointX)) 504 | 505 | + carousel.currentLandPos); 506 | 507 | carousel.element.dispatchEvent(new Event("carouselChangePos")); 508 | carousel.element.dispatchEvent(new Event("carouselPan")); 509 | 510 | } else if (verticalPan && carousel.options.swipeOut) { 511 | e.preventDefault(); 512 | 513 | var slideOutWrap = document.querySelector(".itemslide_slideoutwrap"); 514 | 515 | if (slideOutWrap) { 516 | setTranslate3d(slideOutWrap, 0, touch.pageY - startPointY); 517 | } 518 | 519 | if (verticalSlideFirstTimeCount != -1) { 520 | verticalSlideFirstTimeCount = 1; 521 | } 522 | } 523 | } 524 | 525 | function touchend(e) 526 | { 527 | if (!isDown) { 528 | return; 529 | } 530 | 531 | isDown = false; 532 | 533 | var touch; 534 | 535 | if (e.type == 'touchend') { 536 | touch = getTouch(e); 537 | } 538 | else { 539 | touch = e; 540 | } 541 | 542 | window.removeEventListener('mousemove', mousemove); 543 | window.removeEventListener('touchmove', mousemove); 544 | 545 | if (verticalPan && carousel.options.swipeOut) { 546 | verticalPan = false; 547 | 548 | carousel.swipeOut(); 549 | 550 | return; 551 | } else if (carousel.element.endAnimation && !carousel.options.disableSlide) { 552 | var deltaTime = (Date.now() - swipeStartTime); 553 | deltaTime++; 554 | carousel.vars.velocity = -(touch.pageX - startPointX) / deltaTime; 555 | 556 | if (carousel.vars.velocity > 0) { 557 | carousel.vars.direction = 1; 558 | } else { 559 | carousel.vars.direction = -1; 560 | } 561 | 562 | carousel.vars.distanceFromStart = (touch.pageX - startPointX) * carousel.vars.direction * -1; 563 | var landingSlideIndex = getLandingSlideIndex(carousel.vars.velocity * carousel.options.swipeSensitivity - getTranslate3d(carousel.element).x); 564 | 565 | if (carousel.vars.distanceFromStart > 6) { 566 | gotoSlideByIndex(landingSlideIndex); 567 | return; 568 | } 569 | } 570 | 571 | var clickSlideEvent = new Event("carouselClickSlide"); 572 | 573 | clickSlideEvent.slideIndex = carousel.element.savedSlideIndex; 574 | 575 | carousel.element.dispatchEvent(clickSlideEvent); 576 | 577 | if (carousel.element.savedSlideIndex != carousel.vars.currentIndex && !carousel.options.disableClickToSlide) { 578 | e.preventDefault(); 579 | gotoSlideByIndex(carousel.element.savedSlideIndex); 580 | } 581 | } 582 | 583 | function getTouch(e) 584 | { 585 | if (e.type == "touchmove") { 586 | return e.changedTouches[0]; 587 | } 588 | 589 | return e.touches[0] || e.changedTouches[0]; 590 | } 591 | 592 | function addMousewheel() { 593 | var touchCounter = 0, sensetivity = 4; 594 | 595 | carousel.element.addEventListener("wheel", (e) => { 596 | if (!getVerticalPan()) { 597 | var deltaY = e.deltaY; 598 | var deltaX = e.deltaX; 599 | var delta = e.wheelDelta; 600 | 601 | var isWheel = (delta >= 100 || e.delta % 1 == 0); 602 | 603 | if (!isWheel) { 604 | touchCounter++; 605 | 606 | if (touchCounter == sensetivity) { 607 | touchCounter = 0; 608 | return; 609 | } 610 | } 611 | 612 | e.preventDefault(); 613 | var mouseLandingIndex = carousel.vars.currentIndex - (((deltaX == 0 ? deltaY : deltaX) > 0) ? -1 : 1); 614 | 615 | if (mouseLandingIndex >= carousel.element.children.length || mouseLandingIndex < 0) { 616 | return; 617 | } 618 | 619 | carousel.vars.velocity = 0; 620 | 621 | gotoSlideByIndex(mouseLandingIndex); 622 | } 623 | }); 624 | } 625 | 626 | var Carousel = { 627 | create: function (instance, options, element) { 628 | carousel = this; 629 | 630 | carousel.element = element; 631 | carousel.options = options; 632 | 633 | if (carousel.options.parentWidth) { 634 | element.style.width = element.parentElement.offsetWidth; 635 | } 636 | 637 | element.style.userSelect = "none"; 638 | 639 | carousel.getSlidesWidth = (allSlides = true, maxIndex = 0) => { 640 | var totalWidth = 0; 641 | 642 | if (allSlides) { 643 | maxIndex = element.children.length; 644 | } 645 | 646 | for (var i = 0; i < maxIndex; i++) { 647 | var item = element.children[i]; 648 | 649 | totalWidth += item.offsetWidth 650 | + parseInt(getComputedStyle(item).marginLeft) 651 | + parseInt(getComputedStyle(item).marginRight); 652 | } 653 | 654 | return totalWidth; 655 | }; 656 | 657 | carousel.adjustCarouselWidthIfNotDisabled = () => { 658 | if (!carousel.options.disableAutoWidth) { 659 | element.style.width = carousel.getSlidesWidth() + 10 + "px"; 660 | } 661 | }; 662 | 663 | carousel.adjustCarouselWidthIfNotDisabled(); 664 | 665 | carousel.vars = { 666 | currentIndex: 0, 667 | parentWidth: carousel.options.parentWidth, 668 | velocity: 0, 669 | slideHeight: element.children[0].offsetHeight, 670 | direction: 1, 671 | allSlidesWidth: getCurrentTotalWidth(element), 672 | instance: instance 673 | }; 674 | 675 | element.endAnimation = true; 676 | 677 | if (carousel.options.swipeOut) { 678 | slideout(carousel); 679 | } 680 | 681 | setTranslate3d(element, 0); 682 | gotoSlideByIndex(parseInt(carousel.options.start)); 683 | createEvents(); 684 | 685 | if (!carousel.options.disableScroll) { 686 | addMousewheel(); 687 | } 688 | } 689 | }; 690 | 691 | function addExternalFunctions(itemslide, element, carousel) 692 | { 693 | itemslide.gotoSlide = function (i, noAnimation) { 694 | gotoSlideByIndex(i, noAnimation); 695 | }; 696 | 697 | itemslide.nextSlide = function () { 698 | gotoSlideByIndex(carousel.vars.currentIndex + 1); 699 | }; 700 | 701 | itemslide.previousSlide = function () { 702 | gotoSlideByIndex(carousel.vars.currentIndex - 1); 703 | }; 704 | 705 | itemslide.reload = function (noAnimation) { 706 | var element = carousel.element; 707 | var vars = carousel.vars; 708 | 709 | if (element.children.length === 0) { 710 | return; 711 | } 712 | 713 | if (carousel.vars.parentWidth) { 714 | Array.from(element.children).forEach((slide) => slide.style.width = element.parentElement.offsetWidth); 715 | } 716 | 717 | carousel.adjustCarouselWidthIfNotDisabled(); 718 | 719 | carousel.vars.slideHeight = element.children[0].offsetHeight; 720 | 721 | carousel.vars.allSlidesWidth = getCurrentTotalWidth(element); 722 | 723 | carousel.vars.velocity = 0; 724 | 725 | itemslide.gotoSlide(carousel.vars.currentIndex, noAnimation); 726 | }; 727 | 728 | itemslide.addSlide = function (data) { 729 | var newSlide = document.createElement("li"); 730 | newSlide.innerHTML = data; 731 | 732 | element.appendChild(newSlide); 733 | 734 | carousel.nav.createEvents(); 735 | 736 | itemslide.reload(); 737 | }; 738 | 739 | itemslide.removeSlide = function (index) { 740 | if (carousel.vars.currentIndex === carousel.element.children.length - 1) { 741 | carousel.vars.currentIndex -= 1; 742 | } 743 | 744 | carousel.element.removeChild(carousel.element.children[index || 0]); 745 | carousel.vars.allSlidesWidth = getCurrentTotalWidth(carousel.element); 746 | 747 | itemslide.reload(true); 748 | }; 749 | 750 | itemslide.getActiveIndex = function () { 751 | return carousel.vars.currentIndex; 752 | }; 753 | 754 | itemslide.getCurrentPos = function () { 755 | return getTranslate3d(element).x; 756 | }; 757 | 758 | itemslide.getIndexByPosition = function(x) { 759 | return getLandingSlideIndex(-x); 760 | }; 761 | } 762 | 763 | var defaults = { 764 | duration: 350, 765 | swipeSensitivity: 150, 766 | disableSlide: false, 767 | disableClickToSlide: false, 768 | disableScroll: false, 769 | start: 0, 770 | oneItem: false, // Set true for "one slide per swipe" navigation (used in the full screen navigation example) 771 | panThreshold: 0.3, // Percentage of slide width 772 | disableAutoWidth: false, 773 | parentWidth: false, 774 | swipeOut: false, // Enable the swipe out feature - enables swiping items out of the carousel 775 | leftSided: false // Restricts the movements to the borders instead of the middle 776 | }; 777 | 778 | function Itemslide(element, options) 779 | { 780 | var optionsMergedWithDefaults = {}; 781 | 782 | Object.assign(optionsMergedWithDefaults, defaults); 783 | Object.assign(optionsMergedWithDefaults, options); 784 | 785 | addExternalFunctions(this, element, Carousel); 786 | 787 | Carousel.create(this, optionsMergedWithDefaults, element); 788 | } 789 | 790 | window.Itemslide = Itemslide; 791 | 792 | })(); 793 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "itemslide", 3 | "version": "2.0.1", 4 | "description": "A simple and beautiful vanilla JavaScript touch carousel", 5 | "main": "itemslide.js", 6 | "scripts": { 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/nir9/itemslide" 11 | }, 12 | "keywords": [ 13 | "carousel", 14 | "touch", 15 | "mobile", 16 | "slide", 17 | "swipe" 18 | ], 19 | "homepage": "https://itemslide.org", 20 | "author": "Nir Lichtman", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/nir9/itemslide/issues" 24 | }, 25 | "devDependencies": { 26 | } 27 | } 28 | --------------------------------------------------------------------------------