curry-swiper.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. <template>
  2. <view class="carousel-3d-container" :style="{height: this.slideHeight + 'px'}">
  3. <view class="carousel-3d-slider" :style="{width: this.slideWidth + 'px', height: this.slideHeight + 'px'}">
  4. <slot></slot>
  5. </view>
  6. </view>
  7. </template>
  8. <script>
  9. /* eslint-disable */
  10. const noop = () => {
  11. }
  12. export default {
  13. name: 'curry-swiper',
  14. props: {
  15. // Number of slides
  16. count: {
  17. type: [Number, String],
  18. default: 0
  19. },
  20. // Slides perspective position
  21. perspective: {
  22. type: [Number, String],
  23. default: 35
  24. },
  25. // Number of slides displayed on each page
  26. display: {
  27. type: [Number, String],
  28. default: 3
  29. },
  30. loop: {
  31. type: Boolean,
  32. default: true
  33. },
  34. // Animation between slides in milliseconds
  35. animationSpeed: {
  36. type: [Number, String],
  37. default: 500
  38. },
  39. // Animation direction
  40. dir: {
  41. type: String,
  42. default: 'ltr'
  43. },
  44. width: {
  45. type: [Number, String],
  46. default: 360
  47. },
  48. height: {
  49. type: [Number, String],
  50. default: 270
  51. },
  52. border: {
  53. type: [Number, String],
  54. default: 1
  55. },
  56. // Space between slides in pixels
  57. space: {
  58. type: [Number, String],
  59. default: 'auto'
  60. },
  61. // Start slide index. First slide has 0 index
  62. startIndex: {
  63. type: [Number, String],
  64. default: 0
  65. },
  66. // Enable navigation by clicking on slide
  67. clickable: {
  68. type: Boolean,
  69. default: true
  70. },
  71. disable3d: {
  72. type: Boolean,
  73. default: false
  74. },
  75. // Minimum distance in pixels to swipe before a slide advance is triggered
  76. minSwipeDistance: {
  77. type: Number,
  78. default: 10
  79. },
  80. // Slide inverse scaling
  81. inverseScaling: {
  82. type: [Number, String],
  83. default: 300
  84. },
  85. onLastSlide: {
  86. type: Function,
  87. default: noop
  88. },
  89. onSlideChange: {
  90. type: Function,
  91. default: noop
  92. },
  93. bias: {
  94. type: String,
  95. default: 'left'
  96. },
  97. onMainSlideClick: {
  98. type: Function,
  99. default: noop
  100. }
  101. },
  102. data () {
  103. return {
  104. viewport: 0,
  105. currentIndex: 0,
  106. total: 0,
  107. dragOffset: 0,
  108. dragStartX: 0,
  109. mousedown: false,
  110. zIndex: 998
  111. }
  112. },
  113. watch: {
  114. count () {
  115. this.computeData()
  116. }
  117. },
  118. computed: {
  119. isLastSlide () {
  120. return this.currentIndex === this.total - 1
  121. },
  122. isFirstSlide () {
  123. return this.currentIndex === 0
  124. },
  125. isNextPossible () {
  126. return !(!this.loop && this.isLastSlide)
  127. },
  128. isPrevPossible () {
  129. return !(!this.loop && this.isFirstSlide)
  130. },
  131. slideWidth () {
  132. const vw = this.viewport
  133. const sw = parseInt(this.width) + (parseInt(this.border, 10) * 2)
  134. return vw < sw ? vw : sw
  135. },
  136. slideHeight () {
  137. const sw = parseInt(this.width, 10) + (parseInt(this.border, 10) * 2)
  138. const sh = parseInt(parseInt(this.height) + (this.border * 2), 10)
  139. const ar = this.calculateAspectRatio(sw, sh)
  140. return this.slideWidth / ar
  141. },
  142. visible () {
  143. const v = (this.display > this.total) ? this.total : this.display
  144. return v
  145. },
  146. hasHiddenSlides () {
  147. return this.total > this.visible
  148. },
  149. leftIndices () {
  150. let n = (this.visible - 1) / 2
  151. n = (this.bias.toLowerCase() === 'left' ? Math.ceil(n) : Math.floor(n))
  152. const indices = []
  153. for (let m = 1; m <= n; m++) {
  154. indices.push((this.dir === 'ltr')
  155. ? (this.currentIndex + m) % (this.total)
  156. : (this.currentIndex - m) % (this.total))
  157. }
  158. return indices
  159. },
  160. rightIndices () {
  161. let n = (this.visible - 1) / 2
  162. n = (this.bias.toLowerCase() === 'right' ? Math.ceil(n) : Math.floor(n))
  163. const indices = []
  164. for (let m = 1; m <= n; m++) {
  165. indices.push((this.dir === 'ltr')
  166. ? (this.currentIndex - m) % (this.total)
  167. : (this.currentIndex + m) % (this.total))
  168. }
  169. return indices
  170. },
  171. leftOutIndex () {
  172. let n = (this.visible - 1) / 2
  173. n = (this.bias.toLowerCase() === 'left' ? Math.ceil(n) : Math.floor(n))
  174. n++
  175. if (this.dir === 'ltr') {
  176. return ((this.total - this.currentIndex - n) <= 0)
  177. ? (-parseInt(this.total - this.currentIndex - n))
  178. : (this.currentIndex + n)
  179. } else {
  180. return (this.currentIndex - n)
  181. }
  182. },
  183. rightOutIndex () {
  184. let n = (this.visible - 1) / 2
  185. n = (this.bias.toLowerCase() === 'right' ? Math.ceil(n) : Math.floor(n))
  186. n++
  187. if (this.dir === 'ltr') {
  188. return (this.currentIndex - n)
  189. } else {
  190. return ((this.total - this.currentIndex - n) <= 0)
  191. ? (-parseInt(this.total - this.currentIndex - n, 10))
  192. : (this.currentIndex + n)
  193. }
  194. }
  195. },
  196. methods: {
  197. /**
  198. * Go to next slide
  199. */
  200. goNext () {
  201. if (this.isNextPossible) {
  202. this.isLastSlide ? this.goSlide(0) : this.goSlide(this.currentIndex + 1)
  203. }
  204. },
  205. /**
  206. * Go to previous slide
  207. */
  208. goPrev () {
  209. if (this.isPrevPossible) {
  210. this.isFirstSlide ? this.goSlide(this.total - 1) : this.goSlide(this.currentIndex - 1)
  211. }
  212. },
  213. /**
  214. * Go to slide
  215. * @param {String} index of slide where to go
  216. */
  217. goSlide (index) {
  218. this.currentIndex = (index < 0 || index > this.total - 1) ? 0 : index
  219. if (this.isLastSlide) {
  220. if (this.onLastSlide !== noop) {
  221. console.warn('onLastSlide deprecated, please use @last-slide')
  222. }
  223. this.onLastSlide(this.currentIndex)
  224. this.$emit('last-slide', this.currentIndex)
  225. }
  226. this.$emit('before-slide-change', this.currentIndex)
  227. setTimeout(() => this.animationEnd(), this.animationSpeed)
  228. },
  229. /**
  230. * Go to slide far slide
  231. */
  232. goFar (index) {
  233. let diff = (index === this.total - 1 && this.isFirstSlide) ? -1 : (index - this.currentIndex)
  234. if (this.isLastSlide && index === 0) {
  235. diff = 1
  236. }
  237. const diff2 = (diff < 0) ? -diff : diff
  238. let timeBuff = 0
  239. let i = 0
  240. while (i < diff2) {
  241. i += 1
  242. const timeout = (diff2 === 1) ? 0 : (timeBuff)
  243. setTimeout(() => (diff < 0) ? this.goPrev(diff2) : this.goNext(diff2), timeout)
  244. timeBuff += (this.animationSpeed / (diff2))
  245. }
  246. },
  247. /**
  248. * Trigger actions when animation ends
  249. */
  250. animationEnd () {
  251. if (this.onSlideChange !== noop) {
  252. console.warn('onSlideChange deprecated, please use @after-slide-change')
  253. }
  254. this.onSlideChange(this.currentIndex)
  255. this.$emit('after-slide-change', this.currentIndex)
  256. },
  257. /**
  258. * Trigger actions when mouse is released
  259. * @param {Object} e The event object
  260. */
  261. handleMouseup () {
  262. this.mousedown = false
  263. this.dragOffset = 0
  264. },
  265. /**
  266. * Trigger actions when mouse is pressed
  267. * @param {Object} e The event object
  268. */
  269. handleMousedown (e) {
  270. if (!e.touches) {
  271. e.preventDefault()
  272. }
  273. this.mousedown = true
  274. this.dragStartX = ('ontouchstart' in window) ? e.touches[0].clientX : e.clientX
  275. },
  276. /**
  277. * Trigger actions when mouse is pressed and then moved (mouse drag)
  278. * @param {Object} e The event object
  279. */
  280. handleMousemove (e) {
  281. if (!this.mousedown) {
  282. return
  283. }
  284. const eventPosX = ('ontouchstart' in window) ? e.touches[0].clientX : e.clientX
  285. const deltaX = (this.dragStartX - eventPosX)
  286. this.dragOffset = deltaX
  287. if (this.dragOffset > this.minSwipeDistance) {
  288. this.handleMouseup()
  289. this.goNext()
  290. } else if (this.dragOffset < -this.minSwipeDistance) {
  291. this.handleMouseup()
  292. this.goPrev()
  293. }
  294. },
  295. /**
  296. * A mutation observer is used to detect changes to the containing node
  297. * in order to keep the magnet container in sync with the height its reference node.
  298. */
  299. attachMutationObserver () {
  300. const MutationObserver = window.MutationObserver ||
  301. window.WebKitMutationObserver ||
  302. window.MozMutationObserver
  303. if (MutationObserver) {
  304. const config = {
  305. attributes: true,
  306. childList: true,
  307. characterData: true
  308. }
  309. this.mutationObserver = new MutationObserver(() => {
  310. this.$nextTick(() => {
  311. this.computeData()
  312. })
  313. })
  314. if (this.$el) {
  315. this.mutationObserver.observe(this.$el, config)
  316. }
  317. }
  318. },
  319. /**
  320. * Stop listening to mutation changes
  321. */
  322. detachMutationObserver () {
  323. if (this.mutationObserver) {
  324. this.mutationObserver.disconnect()
  325. }
  326. },
  327. /**
  328. * Get the number of slides
  329. * @return {Number} Number of slides
  330. */
  331. getSlideCount () {
  332. if (this.$slots.default !== undefined) {
  333. return this.$slots.default.filter((value) => {
  334. return value.tag !== void 0
  335. }).length
  336. }
  337. return 0
  338. },
  339. /**
  340. * Calculate slide with and keep defined aspect ratio
  341. * @return {Number} Aspect ratio number
  342. */
  343. calculateAspectRatio (width, height) {
  344. return Math.min(width / height)
  345. },
  346. /**
  347. * Re-compute the number of slides and current slide
  348. */
  349. computeData (firstRun) {
  350. this.total = this.getSlideCount()
  351. if (firstRun || this.currentIndex >= this.total) {
  352. this.currentIndex = parseInt(this.startIndex) > this.total - 1 ? this.total - 1 : parseInt(this.startIndex)
  353. }
  354. this.viewport = this.$el.clientWidth
  355. },
  356. setSize () {
  357. this.$el.style.cssText += 'height:' + this.slideHeight + 'px;'
  358. this.$el.childNodes[0].style.cssText += 'width:' + this.slideWidth + 'px;' + ' height:' + this.slideHeight + 'px;'
  359. }
  360. },
  361. mounted () {
  362. this.computeData(true)
  363. this.attachMutationObserver()
  364. if (!this.$isServer) {
  365. window.addEventListener('resize', this.setSize)
  366. if ('ontouchstart' in window) {
  367. this.$el.addEventListener('touchstart', this.handleMousedown)
  368. this.$el.addEventListener('touchend', this.handleMouseup)
  369. this.$el.addEventListener('touchmove', this.handleMousemove)
  370. } else {
  371. this.$el.addEventListener('mousedown', this.handleMousedown)
  372. this.$el.addEventListener('mouseup', this.handleMouseup)
  373. this.$el.addEventListener('mousemove', this.handleMousemove)
  374. }
  375. }
  376. },
  377. beforeDestroy () {
  378. if (!this.$isServer) {
  379. this.detachMutationObserver()
  380. if ('ontouchstart' in window) {
  381. this.$el.removeEventListener('touchmove', this.handleMousemove)
  382. } else {
  383. this.$el.removeEventListener('mousemove', this.handleMousemove)
  384. }
  385. window.removeEventListener('resize', this.setSize)
  386. }
  387. }
  388. }
  389. </script>
  390. <style lang="scss" scoped>
  391. .carousel-3d-container {
  392. min-height: 1px;
  393. width: 100%;
  394. position: relative;
  395. z-index: 0;
  396. overflow: hidden;
  397. margin: 0px auto;
  398. box-sizing: border-box;
  399. }
  400. .carousel-3d-slider {
  401. position: relative;
  402. margin: 0 auto;
  403. transform-style: preserve-3d;
  404. -webkit-perspective: 1000px;
  405. -moz-perspective: 1000px;
  406. perspective: 1000px;
  407. }
  408. </style>