PoemList.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. <template>
  2. <div class="poem-list">
  3. <div
  4. ref="scrollTarget"
  5. class="scroll-target"
  6. >
  7. <div
  8. class="layer-4"
  9. :style="{
  10. left: layer4Left + 'px',
  11. }"
  12. >
  13. <img
  14. class="bg"
  15. src="@/assets/images/poem-list/bg.jpg"
  16. alt=""
  17. draggable="false"
  18. >
  19. <img
  20. class="bg"
  21. src="@/assets/images/poem-list/bg.jpg"
  22. alt=""
  23. draggable="false"
  24. >
  25. <img
  26. class="bg"
  27. src="@/assets/images/poem-list/bg.jpg"
  28. alt=""
  29. draggable="false"
  30. >
  31. </div>
  32. <div
  33. class="layer-3"
  34. :style="{
  35. left: layer3Left + 'px',
  36. }"
  37. >
  38. <img
  39. class="bamboo"
  40. src="@/assets/images/poem-list/3-min.png"
  41. alt=""
  42. draggable="false"
  43. >
  44. <img
  45. class="bamboo"
  46. src="@/assets/images/poem-list/3-min.png"
  47. alt=""
  48. draggable="false"
  49. >
  50. </div>
  51. <div
  52. class="layer-2"
  53. :style="{
  54. left: layer2Left + 'px',
  55. }"
  56. >
  57. <img
  58. class="bamboo"
  59. src="@/assets/images/poem-list/2-min.png"
  60. alt=""
  61. draggable="false"
  62. >
  63. <img
  64. class="bamboo"
  65. src="@/assets/images/poem-list/2-min.png"
  66. alt=""
  67. draggable="false"
  68. >
  69. </div>
  70. <div
  71. class="layer-1"
  72. :style="{
  73. left: layer1Left + 'px',
  74. }"
  75. >
  76. <img
  77. class="bamboo"
  78. src="@/assets/images/poem-list/1-min.png"
  79. alt=""
  80. draggable="false"
  81. >
  82. <img
  83. class="bamboo"
  84. src="@/assets/images/poem-list/1-min.png"
  85. alt=""
  86. draggable="false"
  87. >
  88. <!-- 诗句内容 -->
  89. <div
  90. v-for="(item, index) in poemList"
  91. :key="index"
  92. class="poem"
  93. >
  94. <div class="title-wrap">
  95. <h1>《{{ item["标题"] }}》</h1>
  96. <div class="sub-title">
  97. <span class="author">{{ item["作者"] }}</span>
  98. <span class="age">{{ item["朝代"] }}</span>
  99. </div>
  100. </div>
  101. <p>{{ item["正文"] }}</p>
  102. </div>
  103. </div>
  104. </div>
  105. <div class="shadow-bottom">
  106. <menu class="age-list">
  107. <button
  108. v-for="(item, idx) in ageList"
  109. :key="item.id"
  110. class="age"
  111. :class="{
  112. active: activeAgeIdx === idx,
  113. }"
  114. @click="onClickAge(item)"
  115. >
  116. <img
  117. class="img-normal"
  118. :src="require(`@/assets/images/poem-list/button-${item.id}.png`)"
  119. alt=""
  120. draggable="false"
  121. >
  122. <img
  123. class="img-active"
  124. :src="require(`@/assets/images/poem-list/button-${item.id}-active.png`)"
  125. alt=""
  126. draggable="false"
  127. >
  128. </button>
  129. </menu>
  130. </div>
  131. <!-- todo -->
  132. <BtnBack
  133. color="green"
  134. @click="router.go('-1')"
  135. />
  136. <OperationTip
  137. v-show="isShowOperationTip"
  138. class="operation-tip"
  139. :text="'向下滑动滚轮'"
  140. :color="'green'"
  141. />
  142. </div>
  143. </template>
  144. <script setup>
  145. import { ref, computed, watch, onBeforeMount, nextTick } from "vue"
  146. import { useRoute, useRouter } from "vue-router"
  147. import { useStore } from "vuex"
  148. import useSmoothSwipe from "@/useFunctions/useSmoothSwipe.js"
  149. import { useWindowSize } from "@vueuse/core"
  150. import OperationTip from "@/components/OperationTip.vue"
  151. const route = useRoute()
  152. const router = useRouter()
  153. const store = useStore()
  154. const windowWidthDesign = 7681 * 2
  155. const windowHeightDesign = 1080 - 71 - 37 // 设计稿里视口高度。注意要减去上下边栏
  156. const scrollTarget = ref(null)
  157. const { width: windowWidth, height: windowHeight } = useWindowSize()
  158. const maxTranslateLength = computed(() => {
  159. return (windowHeight.value * windowWidthDesign) / windowHeightDesign - 800 * windowHeight.value / windowHeightDesign // 有的图层不够长导致移动到最右侧不好看,隐藏掉。
  160. })
  161. const {
  162. translateLength,
  163. } = useSmoothSwipe({
  164. scrollTargetRef: scrollTarget,
  165. maxTranslateLength,
  166. viewportWidth: windowWidth,
  167. })
  168. // layer4Left位移
  169. const layer4SpeedFactor = 0.6
  170. const layer4InitialLeft = 0
  171. const layer4Left = ref(layer4InitialLeft)
  172. // layer3Left位移
  173. const layer3SpeedFactor = 0.8
  174. const layer3InitialLeft = 0
  175. const layer3Left = ref(layer3InitialLeft)
  176. // layer2Left位移
  177. const layer2SpeedFactor = 1
  178. const layer2InitialLeft = 0
  179. const layer2Left = ref(layer2InitialLeft)
  180. // layer1Left位移
  181. const layer1InitialLeft = 0
  182. const layer1Left = ref(layer1InitialLeft)
  183. watch(
  184. translateLength,
  185. (v) => {
  186. layer4Left.value = layer4InitialLeft - v * layer4SpeedFactor
  187. layer3Left.value = layer3InitialLeft - v * layer3SpeedFactor
  188. layer2Left.value = layer2InitialLeft - v * layer2SpeedFactor
  189. layer1Left.value = layer1InitialLeft - v
  190. },
  191. {
  192. immediate: true,
  193. }
  194. )
  195. const poemList = configExcel["诗词"]
  196. const ageList = [
  197. {
  198. name: '唐',
  199. id: 'tang',
  200. startPos: 0,
  201. },
  202. {
  203. name: '宋',
  204. id: 'song',
  205. startPos: 3000,
  206. },
  207. {
  208. name: '元',
  209. id: 'yuan',
  210. startPos: 5500,
  211. },
  212. {
  213. name: '明',
  214. id: 'ming',
  215. startPos: 8500
  216. },
  217. {
  218. name: '清',
  219. id: 'qing',
  220. startPos: 11200
  221. },
  222. ]
  223. function onClickAge(item) {
  224. translateLength.value = item.startPos * windowHeight.value / windowHeightDesign
  225. }
  226. const activeAgeIdx = ref(0)
  227. watch(translateLength, (v) => {
  228. for (let index = ageList.length - 1; index >= 0; index--) {
  229. const element = ageList[index]
  230. if (element.startPos * windowHeight.value / windowHeightDesign <= translateLength.value ) {
  231. activeAgeIdx.value = index
  232. break
  233. }
  234. }
  235. })
  236. const isShowOperationTip = ref(true)
  237. const unwatch = watch(translateLength, (v) => {
  238. if (v) {
  239. isShowOperationTip.value = false
  240. unwatch()
  241. }
  242. })
  243. </script>
  244. <style lang="less" scoped>
  245. .poem-list {
  246. background-color: #fefefe;
  247. position: absolute;
  248. left: 0;
  249. top: 0;
  250. width: 100%;
  251. height: 100%;
  252. user-select: none;
  253. > .scroll-target {
  254. height: 100%;
  255. width: 100%;
  256. display: flex;
  257. gap: 100px;
  258. overflow: hidden;
  259. > .layer-4 {
  260. position: absolute;
  261. height: 100%;
  262. display: flex;
  263. > .bg {
  264. flex: 0 0 auto;
  265. height: 100%;
  266. }
  267. > .bg:nth-of-type(2) {
  268. transform: rotate3d(0, 1, 0, 180deg);
  269. }
  270. }
  271. > .layer-3 {
  272. position: absolute;
  273. height: 33%;
  274. bottom: 0;
  275. display: flex;
  276. > .bamboo {
  277. flex: 0 0 auto;
  278. height: 100%;
  279. }
  280. > .bamboo:nth-of-type(1){
  281. margin-left: calc((1250px) * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  282. }
  283. > .bamboo:nth-of-type(2){
  284. margin-left: calc((171px + 1250px) * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  285. }
  286. }
  287. > .layer-2 {
  288. position: absolute;
  289. height: 100%;
  290. display: flex;
  291. > .bamboo {
  292. flex: 0 0 auto;
  293. height: 100%;
  294. }
  295. > .bamboo:nth-of-type(1){
  296. margin-left: calc((193px) * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  297. }
  298. > .bamboo:nth-of-type(2){
  299. margin-left: calc((720px + 193px) * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  300. }
  301. }
  302. > .layer-1 {
  303. position: absolute;
  304. height: 100%;
  305. display: flex;
  306. > .bamboo {
  307. flex: 0 0 auto;
  308. height: 100%;
  309. }
  310. > .bamboo:nth-of-type(1){
  311. margin-left: calc((418px) * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  312. }
  313. > .bamboo:nth-of-type(2){
  314. margin-left: calc((800px + 418px) * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  315. }
  316. >.poem {
  317. position: absolute;
  318. left: 0;
  319. top: 10.1%;
  320. writing-mode: vertical-rl;
  321. > .title-wrap {
  322. position: relative;
  323. width: fit-content;
  324. height: fit-content;
  325. > h1 {
  326. font-family: KingHwa_OldSong, KingHwa_OldSong;
  327. font-weight: 400;
  328. font-size: calc(48px * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  329. color: #303030;
  330. line-height: calc(56px * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  331. white-space: pre;
  332. }
  333. > .sub-title {
  334. position: absolute;
  335. left: 0;
  336. top: 50%;
  337. transform: translate(-140%, -50%);
  338. display: flex;
  339. align-items: center;
  340. > .author {
  341. white-space: pre;
  342. font-family: KaiTi;
  343. font-weight: 400;
  344. font-size: calc(24px * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  345. color: #BAA565;
  346. margin-inline-end: 0.5em;
  347. }
  348. > .age {
  349. display: inline-block;
  350. width: calc(24px * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  351. height: calc(24px * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  352. background-color: #b6a261;
  353. border-radius: 50%;
  354. display: flex;
  355. justify-content: center;
  356. align-items: center;
  357. font-family: KaiTi;
  358. font-weight: 400;
  359. font-size: calc(20px * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  360. color: #ffffff;
  361. }
  362. }
  363. }
  364. > p {
  365. position: absolute;
  366. right: calc(120px * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  367. top: 75%;
  368. font-family: KaiTi;
  369. font-weight: 400;
  370. font-size: calc(26px * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  371. color: #303030;
  372. line-height: 1.6em;
  373. white-space: pre;
  374. letter-spacing: 0.2em;
  375. }
  376. }
  377. .poem:nth-of-type(1) {
  378. top: calc(210px * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  379. left: calc(260px * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  380. }
  381. .poem:nth-of-type(2) {
  382. left: calc(830px * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  383. }
  384. .poem:nth-of-type(3) {
  385. left: calc(1700px * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  386. }
  387. .poem:nth-of-type(4) {
  388. left: calc(2500px * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  389. }
  390. .poem:nth-of-type(5) {
  391. left: calc(3550px * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  392. >p{
  393. transform: translateY(-15%);
  394. }
  395. }
  396. .poem:nth-of-type(6) {
  397. left: calc(3950px * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  398. }
  399. .poem:nth-of-type(7) {
  400. left: calc(4300px * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  401. }
  402. .poem:nth-of-type(8) {
  403. left: calc(5100px * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  404. }
  405. .poem:nth-of-type(9) {
  406. left: calc(6000px * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  407. >p{
  408. transform: translateY(-12%);
  409. }
  410. }
  411. .poem:nth-of-type(10) {
  412. left: calc(6800px * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  413. }
  414. .poem:nth-of-type(11) {
  415. transform: translateY(40%);
  416. left: calc((7200px + 260px) * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  417. }
  418. .poem:nth-of-type(12) {
  419. left: calc((7200px + 830px) * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  420. >p{
  421. transform: translateY(-40%);
  422. }
  423. }
  424. .poem:nth-of-type(13) {
  425. left: calc((7200px + 1700px) * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  426. }
  427. .poem:nth-of-type(14) {
  428. left: calc((7200px + 2500px) * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  429. }
  430. .poem:nth-of-type(15) {
  431. left: calc((7200px + 3550px) * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  432. }
  433. .poem:nth-of-type(16) {
  434. left: calc((7200px + 3950px) * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  435. }
  436. .poem:nth-of-type(17) {
  437. left: calc((7200px + 4300px) * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  438. }
  439. .poem:nth-of-type(18) {
  440. left: calc((7200px + 5100px) * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  441. }
  442. .poem:nth-of-type(19) {
  443. transform: translateY(-10%);
  444. left: calc((7200px + 6000px) * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  445. >p{
  446. transform: translateY(-80%);
  447. }
  448. }
  449. .poem:nth-of-type(20) {
  450. left: calc((7200px + 6800px) * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  451. }
  452. }
  453. }
  454. >.shadow-bottom{
  455. position: absolute;
  456. left: 0;
  457. bottom: 0;
  458. width: 100%;
  459. height: 12.4%;
  460. background: linear-gradient(0deg, #888 0%, rgba(0,0,0,0) 100%);
  461. >menu.age-list{
  462. position: absolute;
  463. left: 50%;
  464. bottom: calc(30px / v-bind('windowHeightDesign') * v-bind('windowHeight'));
  465. transform: translateX(-50%);
  466. display: flex;
  467. align-items: center;
  468. gap: calc(20px / v-bind('windowHeightDesign') * v-bind('windowHeight'));
  469. >button.age{
  470. width: calc(47px / v-bind('windowHeightDesign') * v-bind('windowHeight'));
  471. height: calc(47px / v-bind('windowHeightDesign') * v-bind('windowHeight'));
  472. position: relative;
  473. >img{
  474. position: absolute;
  475. left: 0;
  476. top: 0;
  477. width: 100%;
  478. height: 100%;
  479. }
  480. >img.img-normal{
  481. display: block;
  482. }
  483. >img.img-active{
  484. display: none;
  485. }
  486. }
  487. >button.age:hover, button.age.active{
  488. >img.img-normal{
  489. display: none;
  490. }
  491. >img.img-active{
  492. display: block;
  493. }
  494. }
  495. }
  496. }
  497. > .operation-tip {
  498. position: absolute;
  499. left: 42%;
  500. bottom: calc(234px * v-bind('windowHeight') / v-bind('windowHeightDesign'));
  501. transform: translateX(-50%);
  502. }
  503. }
  504. </style>