PoemList.vue 14 KB

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