parallax-home.vue 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. <script setup lang="ts">
  2. import { computed, reactive, ref } from 'vue'
  3. import { useEventListener, useParallax, useThrottleFn } from '@vueuse/core'
  4. import { useLang } from '../../composables/lang'
  5. import homeLocale from '../../../i18n/pages/home.json'
  6. import HomeSponsors from '../home/home-sponsors.vue'
  7. import HomeCards from '../home/home-cards.vue'
  8. import HomeFooter from './vp-footer.vue'
  9. import type { CSSProperties } from 'vue'
  10. const target = ref<HTMLElement | null>(null)
  11. const parallax = reactive(useParallax(target))
  12. const jumbotronRedOffset = ref(0)
  13. const jumbotronRef = ref<HTMLElement | null>(null)
  14. const lang = useLang()
  15. const homeLang = computed(() => homeLocale[lang.value])
  16. function jumpTo(path: string) {
  17. // vitepress has not router
  18. location.href = `/${lang.value}/${path}`
  19. }
  20. const containerStyle: CSSProperties = {
  21. display: 'flex',
  22. flexDirection: 'column',
  23. justifyContent: 'center',
  24. alignItems: 'center',
  25. transition: '.3s ease-out all',
  26. position: 'relative',
  27. perspective: '300px',
  28. }
  29. const cardStyle = computed(() => ({
  30. height: '30rem',
  31. width: '100%',
  32. transition: '.3s ease-out all',
  33. transform: `rotateX(${parallax.roll}deg) rotateY(${parallax.tilt}deg)`,
  34. }))
  35. const layerBase: CSSProperties = {
  36. position: 'absolute',
  37. width: '100%',
  38. height: '100%',
  39. transition: '.3s ease-out all',
  40. }
  41. const screenLayer = computed(() => ({
  42. ...layerBase,
  43. width: '80%',
  44. height: '80%',
  45. transform: `translateX(${parallax.tilt * 10 + 80}px) translateY(${
  46. parallax.roll * 10 + 50
  47. }px)`,
  48. }))
  49. const peopleLayer = computed(() => ({
  50. ...layerBase,
  51. width: '30%',
  52. height: '30%',
  53. right: 0,
  54. bottom: 0,
  55. transform: `translateX(${parallax.tilt * 25 + 25}px) translateY(${
  56. parallax.roll * 25
  57. }px) scale(1)`,
  58. }))
  59. // center layer
  60. const leftLayer = computed(() => ({
  61. ...layerBase,
  62. width: '20%',
  63. height: '20%',
  64. transform: `translateX(${parallax.tilt * 12 + 205}px) translateY(${
  65. parallax.roll * 12 + 210
  66. }px)`,
  67. }))
  68. const leftBottomLayer = computed(() => ({
  69. ...layerBase,
  70. width: '30%',
  71. height: '30%',
  72. left: 0,
  73. bottom: 0,
  74. transform: `translateX(${parallax.tilt * 30 - 10}px) translateY(${
  75. parallax.roll * 30
  76. }px)`,
  77. }))
  78. const rightLayer = computed(() => ({
  79. ...layerBase,
  80. width: '33%',
  81. height: '33%',
  82. top: 0,
  83. right: 0,
  84. transform: `translateX(${parallax.tilt * 25 + 5}px) translateY(${
  85. parallax.roll * 25
  86. }px)`,
  87. }))
  88. const handleScroll = useThrottleFn(() => {
  89. const ele = jumbotronRef.value
  90. if (ele) {
  91. const rect = ele.getBoundingClientRect()
  92. const eleHeight = ele.clientHeight
  93. let calHeight = (180 - rect.top) * 2
  94. if (calHeight < 0) calHeight = 0
  95. if (calHeight > eleHeight) calHeight = eleHeight
  96. jumbotronRedOffset.value = calHeight
  97. }
  98. }, 10)
  99. useEventListener(window, 'scroll', handleScroll)
  100. </script>
  101. <template>
  102. <div ref="target" class="home-page">
  103. <div class="banner" text="center">
  104. <div class="banner-desc" m="t-4">
  105. <h1>{{ homeLang['title'] }}</h1>
  106. <p m="t-2">{{ homeLang['title_sub'] }}</p>
  107. </div>
  108. </div>
  109. <div ref="jumbotronRef" class="jumbotron">
  110. <div class="parallax-container" :style="containerStyle">
  111. <div :style="cardStyle">
  112. <screen-svg :style="screenLayer" alt="banner" />
  113. <people-svg
  114. :style="peopleLayer"
  115. alt="banner"
  116. class="cursor-pointer"
  117. @click="jumpTo('guide/quickstart.html')"
  118. />
  119. <left-layer-svg :style="leftLayer" alt="banner" />
  120. <left-bottom-layer-svg :style="leftBottomLayer" alt="banner" />
  121. <right-layer-svg :style="rightLayer" alt="banner" />
  122. </div>
  123. </div>
  124. </div>
  125. <img
  126. src="/images/theme-index-blue.png"
  127. alt="banner"
  128. class="mobile-banner"
  129. />
  130. <HomeSponsors />
  131. <HomeCards />
  132. </div>
  133. <HomeFooter :is-home="true" />
  134. </template>
  135. <style lang="scss">
  136. @use '../../styles/mixins' as *;
  137. .home-page {
  138. .mobile-banner {
  139. display: none;
  140. }
  141. .banner-dot h1 span {
  142. position: relative;
  143. &::after {
  144. content: '';
  145. position: absolute;
  146. right: -12px;
  147. bottom: 8px;
  148. background: var(--el-color-primary);
  149. height: 8px;
  150. width: 8px;
  151. border-radius: 100%;
  152. }
  153. }
  154. .banner-desc {
  155. h1 {
  156. font-size: 34px;
  157. margin: 0;
  158. line-height: 48px;
  159. color: var(--text-color);
  160. }
  161. p {
  162. font-size: 18px;
  163. color: var(--text-color-light);
  164. }
  165. }
  166. .count-down {
  167. .cd-main {
  168. background: #f1f6fe;
  169. border-radius: 10px;
  170. width: 50%;
  171. margin: 60px auto 120px;
  172. padding: 30px 0;
  173. font-size: 24px;
  174. color: #666;
  175. text-align: center;
  176. font-weight: 600;
  177. }
  178. .cd-date {
  179. font-size: 28px;
  180. }
  181. .cd-time {
  182. display: flex;
  183. justify-content: space-between;
  184. width: 80%;
  185. margin: 10px auto 0;
  186. }
  187. .cd-num {
  188. color: var(--el-color-primary);
  189. font-size: 78px;
  190. font-weight: bold;
  191. }
  192. .cd-num span {
  193. width: 50%;
  194. display: inline-block;
  195. }
  196. .cd-str {
  197. font-size: 22px;
  198. margin-top: -5px;
  199. }
  200. }
  201. .jumbotron {
  202. width: 800px;
  203. margin: 20px auto;
  204. position: relative;
  205. img {
  206. width: 100%;
  207. }
  208. .parallax-container {
  209. width: 800px;
  210. }
  211. }
  212. @media screen and (max-width: 959px) {
  213. .jumbotron {
  214. display: none !important;
  215. }
  216. .mobile-banner {
  217. display: inline-block;
  218. }
  219. }
  220. @media (max-width: 768px) {
  221. .jumbotron {
  222. width: 50%;
  223. display: flex;
  224. margin: auto;
  225. justify-content: center;
  226. align-items: center;
  227. .parallax-container {
  228. width: 100%;
  229. }
  230. }
  231. }
  232. @media (max-width: 768px) {
  233. .banner-desc {
  234. padding-top: 0px;
  235. }
  236. .cards {
  237. li {
  238. width: 80%;
  239. margin: 0 auto 20px;
  240. float: none;
  241. }
  242. .card {
  243. height: auto;
  244. padding-bottom: 54px;
  245. }
  246. }
  247. .banner-stars {
  248. display: none;
  249. }
  250. .banner-desc {
  251. h1 {
  252. font-size: 22px;
  253. }
  254. #line2 {
  255. display: none;
  256. }
  257. h2 {
  258. font-size: 32px;
  259. }
  260. p {
  261. width: auto;
  262. }
  263. }
  264. .banner-dot h1 span {
  265. &::after {
  266. right: -8px;
  267. bottom: 2px;
  268. height: 6px;
  269. width: 6px;
  270. }
  271. }
  272. .count-down {
  273. .cd-main {
  274. width: 90%;
  275. margin: 40px auto 40px;
  276. padding: 20px 0;
  277. }
  278. .cd-date {
  279. font-size: 22px;
  280. }
  281. .cd-num {
  282. font-size: 38px;
  283. }
  284. .cd-str {
  285. font-size: 12px;
  286. margin-top: 0px;
  287. }
  288. }
  289. .sponsors-list {
  290. display: flex;
  291. flex-direction: column;
  292. align-content: center;
  293. .sponsor {
  294. justify-content: left;
  295. }
  296. }
  297. }
  298. .theme-intro-b {
  299. position: fixed;
  300. left: 0;
  301. right: 0;
  302. top: 0;
  303. bottom: 0;
  304. z-index: 200;
  305. .intro-banner {
  306. position: absolute;
  307. }
  308. img {
  309. width: 300px;
  310. }
  311. .title {
  312. position: absolute;
  313. top: 0;
  314. bottom: 0;
  315. left: 0;
  316. right: 0;
  317. color: #fff;
  318. text-align: center;
  319. font-weight: bold;
  320. font-size: 20px;
  321. display: flex;
  322. justify-content: center;
  323. align-items: center;
  324. p {
  325. padding: 0;
  326. margin: 10px 0;
  327. }
  328. }
  329. }
  330. .theme-intro-a {
  331. position: fixed;
  332. left: 0;
  333. right: 0;
  334. top: 0;
  335. bottom: 0;
  336. z-index: 200;
  337. .mask {
  338. position: fixed;
  339. left: 0;
  340. right: 0;
  341. top: 0;
  342. bottom: 0;
  343. background: #000;
  344. opacity: 0.5;
  345. }
  346. .intro-banner {
  347. top: 50%;
  348. left: 50%;
  349. position: fixed;
  350. transform: translate(-50%, -50%);
  351. box-sizing: border-box;
  352. text-align: center;
  353. z-index: 100;
  354. img {
  355. width: 100%;
  356. }
  357. .intro-text {
  358. position: absolute;
  359. top: 50%;
  360. left: 0;
  361. right: 0;
  362. p {
  363. padding: 0;
  364. margin: 0;
  365. font-size: 48px;
  366. font-weight: bold;
  367. color: #fff;
  368. }
  369. }
  370. }
  371. }
  372. }
  373. </style>