FullscreenCarousel.vue 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. <template>
  2. <div class="fullscreen-carousel">
  3. <div class="glide" ref="glide">
  4. <div class="glide__track" data-glide-el="track">
  5. <ul class="glide__slides">
  6. <li class="glide__slide" v-for="(item, index) in feed" :key="index">
  7. <div class="slide-content">
  8. <img :src="item.media_url" :alt="item.caption" class="slide-image" loading="lazy">
  9. <div v-if="withOverlay" class="slide-overlay">
  10. <p v-if="withLinks" class="slide-username"><a :href="item.account.url">{{ webfinger }}</a></p>
  11. <p v-else class="slide-username">{{ webfinger }}</p>
  12. <div class="d-flex gap-1">
  13. <div v-if="withLinks" class="slide-date">
  14. <a :href="item.url" target="_blank">{{ formatDate(item.created_at) }}</a>
  15. </div>
  16. <div v-else class="slide-date">{{ formatDate(item.created_at) }}</div>
  17. </div>
  18. </div>
  19. </div>
  20. </li>
  21. </ul>
  22. </div>
  23. <div class="glide__arrows" data-glide-el="controls">
  24. <button class="glide__arrow glide__arrow--left fancy-arrow" data-glide-dir="<">
  25. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  26. <polyline points="15 18 9 12 15 6"></polyline>
  27. </svg>
  28. </button>
  29. <button class="glide__arrow glide__arrow--right fancy-arrow" data-glide-dir=">">
  30. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  31. <polyline points="9 18 15 12 9 6"></polyline>
  32. </svg>
  33. </button>
  34. </div>
  35. </div>
  36. </div>
  37. </template>
  38. <script>
  39. import Glide from '@glidejs/glide'
  40. export default {
  41. props: {
  42. feed: {
  43. type: Array,
  44. required: true
  45. },
  46. canLoadMore: {
  47. type: Boolean,
  48. default: false
  49. },
  50. withLinks: {
  51. type: Boolean,
  52. default: false
  53. },
  54. withOverlay: {
  55. type: Boolean,
  56. default: true
  57. },
  58. autoPlay: {
  59. type: Boolean,
  60. default: false
  61. },
  62. autoPlayInterval: {
  63. type: Number,
  64. default: () => { return 5000; }
  65. }
  66. },
  67. data() {
  68. return {
  69. glideInstance: null
  70. }
  71. },
  72. mounted() {
  73. this.initGlide()
  74. },
  75. computed: {
  76. webfinger: {
  77. get() {
  78. if(this.feed && this.feed.length) {
  79. const account = this.feed[0].account
  80. const domain = new URL(account.url).host
  81. return `@${account.username}@${domain}`
  82. }
  83. return ""
  84. }
  85. }
  86. },
  87. methods: {
  88. initGlide() {
  89. this.glideInstance = new Glide(this.$refs.glide, {
  90. type: 'carousel',
  91. startAt: 0,
  92. perView: 1,
  93. gap: 0,
  94. hoverpause: false,
  95. autoplay: this.autoPlay ? this.autoPlayInterval : false,
  96. keyboard: true
  97. })
  98. this.glideInstance.on('run.after', this.checkForPagination)
  99. this.glideInstance.mount()
  100. },
  101. checkForPagination() {
  102. const currentIndex = this.glideInstance.index
  103. if (currentIndex === this.feed.length - 1 && this.canLoadMore) {
  104. this.$emit('load-more')
  105. }
  106. },
  107. loadMore() {
  108. this.$emit('load-more')
  109. },
  110. formatDate(dateInput, locale = navigator.language) {
  111. let date;
  112. if (typeof dateInput === 'string') {
  113. date = new Date(dateInput);
  114. if (isNaN(date.getTime())) {
  115. throw new Error('Invalid date string. Please provide a valid ISO 8601 format.');
  116. }
  117. } else if (dateInput instanceof Date) {
  118. date = dateInput;
  119. } else {
  120. throw new Error('Invalid input. Please provide a Date object or an ISO 8601 string.');
  121. }
  122. const options = {
  123. year: 'numeric',
  124. month: 'long',
  125. day: 'numeric',
  126. hour: 'numeric',
  127. minute: 'numeric',
  128. hour12: true
  129. };
  130. return new Intl.DateTimeFormat(locale, options).format(date);
  131. },
  132. updateGlide() {
  133. this.$nextTick(() => {
  134. if (this.glideInstance) {
  135. this.glideInstance.update()
  136. }
  137. })
  138. }
  139. },
  140. watch: {
  141. feed() {
  142. this.updateGlide()
  143. }
  144. }
  145. }
  146. </script>
  147. <style scoped lang="scss">
  148. .fullscreen-carousel {
  149. height: 100dvh;
  150. width: 100dvw;
  151. position: relative;
  152. overflow: hidden;
  153. z-index: 2;
  154. background: #000;
  155. }
  156. .glide, .glide__track, .glide__slides, .glide__slide {
  157. height: 100%;
  158. }
  159. .slide-content {
  160. position: relative;
  161. height: 100%;
  162. width: 100%;
  163. }
  164. .slide-image {
  165. object-fit: contain;
  166. width: 100%;
  167. height: 100%;
  168. }
  169. .slide-overlay {
  170. position: absolute;
  171. bottom: 0;
  172. left: 0;
  173. right: 0;
  174. background: rgba(0, 0, 0, 0.5);
  175. color: white;
  176. padding: 8px 20px;
  177. display: flex;
  178. justify-content: space-between;
  179. align-items: center;
  180. gap: 1rem;
  181. }
  182. .gap-1 {
  183. gap: 2rem;
  184. }
  185. .slide-image {
  186. .slide-overlay {
  187. &:not(:hover) {
  188. height: 0;
  189. opacity: 0;
  190. transform: height 1s ease;
  191. }
  192. }
  193. }
  194. .slide-username {
  195. margin: 0;
  196. user-select: all;
  197. font-size: 14px;
  198. a {
  199. color: white;
  200. font-weight: 500;
  201. }
  202. }
  203. .slide-caption {
  204. margin: 0;
  205. font-size: 14px;
  206. }
  207. .slide-date {
  208. margin: 0;
  209. font-size: 14px;
  210. a {
  211. color: white;
  212. font-weight: bold;
  213. text-decoration: none;
  214. }
  215. }
  216. .glide__arrow {
  217. position: absolute;
  218. top: 50%;
  219. transform: translateY(-50%);
  220. background: rgba(255, 255, 255, 0.5);
  221. border: none;
  222. font-size: 24px;
  223. padding: 10px;
  224. cursor: pointer;
  225. }
  226. .fancy-arrow {
  227. position: absolute;
  228. top: 50%;
  229. transform: translateY(-50%);
  230. background: rgba(255, 255, 255, 0.2);
  231. border: none;
  232. border-radius: 50%;
  233. width: 50px;
  234. height: 50px;
  235. display: flex;
  236. justify-content: center;
  237. align-items: center;
  238. cursor: pointer;
  239. transition: all 0.3s ease;
  240. overflow: hidden;
  241. }
  242. .fancy-arrow:hover {
  243. background: rgba(255, 255, 255, 0.4);
  244. box-shadow: 0 0 15px rgba(255, 255, 255, 0.5);
  245. }
  246. .fancy-arrow:focus {
  247. outline: none;
  248. }
  249. .fancy-arrow svg {
  250. width: 24px;
  251. height: 24px;
  252. color: white;
  253. transition: all 0.3s ease;
  254. }
  255. .fancy-arrow:hover svg {
  256. transform: scale(1.2);
  257. }
  258. .glide__arrow--left {
  259. left: 20px;
  260. }
  261. .glide__arrow--right {
  262. right: 20px;
  263. }
  264. @keyframes pulse {
  265. 0% {
  266. transform: translateY(-50%) scale(1);
  267. }
  268. 50% {
  269. transform: translateY(-50%) scale(1.05);
  270. }
  271. 100% {
  272. transform: translateY(-50%) scale(1);
  273. }
  274. }
  275. .fancy-arrow:active {
  276. animation: pulse 0.3s ease-in-out;
  277. }
  278. @media (max-width: 768px) {
  279. .fancy-arrow {
  280. width: 40px;
  281. height: 40px;
  282. }
  283. .fancy-arrow svg {
  284. width: 20px;
  285. height: 20px;
  286. }
  287. .glide__arrow--left {
  288. left: 10px;
  289. }
  290. .glide__arrow--right {
  291. right: 10px;
  292. }
  293. }
  294. </style>