index.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. <template>
  2. <div style="height: 100%">
  3. <div
  4. v-if="hotVisible"
  5. class="bamboo-hot2-hot"
  6. :class="{
  7. left: [7, 8].includes(checkedHotId)
  8. }"
  9. >
  10. <div class="bamboo-hot2-hot__title">
  11. {{ ITEM_INFO_MAP[checkedHotId].label }}
  12. </div>
  13. <p
  14. class="bamboo-hot2-hot__inner"
  15. >
  16. {{ ITEM_INFO_MAP[checkedHotId].info }}
  17. </p>
  18. </div>
  19. <div
  20. ref="bambooWrap"
  21. class="bamboo-hot2"
  22. :class="{
  23. 'wrap-hide': hotVisible
  24. }"
  25. @touchstart="handleTouchstart"
  26. @touchmove="handleTouchmove"
  27. @touchend="handleTouchend"
  28. >
  29. <div
  30. class="bamboo-hot2-b1"
  31. :class="{
  32. hide: hotVisible && checkedHotId !== 1
  33. }"
  34. >
  35. <img src="./images/bamboo1.png">
  36. <div
  37. class="bamboo-hot2__hot"
  38. :class="{
  39. hide: hotVisible
  40. }"
  41. @click="handleHot(1)"
  42. >
  43. <p>水竹</p>
  44. </div>
  45. </div>
  46. <div
  47. class="bamboo-hot2-b2"
  48. :class="{
  49. hide: hotVisible && checkedHotId !== 2
  50. }"
  51. >
  52. <img
  53. src="./images/bamboo2.png"
  54. @load="handleBambooOffset(2)"
  55. >
  56. </div>
  57. <div
  58. class="bamboo-hot2__hot b2"
  59. :class="{
  60. hide: hotVisible
  61. }"
  62. @click="handleHot(2)"
  63. >
  64. <p>紫竹</p>
  65. </div>
  66. <div
  67. class="bamboo-hot2-b3"
  68. :class="{
  69. hide: hotVisible && checkedHotId !== 3
  70. }"
  71. >
  72. <img
  73. src="./images/bamboo3.png"
  74. @load="handleBambooOffset(3)"
  75. >
  76. </div>
  77. <div
  78. class="bamboo-hot2__hot b3"
  79. :class="{
  80. hide: hotVisible
  81. }"
  82. @click="handleHot(3)"
  83. >
  84. <p>梅鹿竹</p>
  85. </div>
  86. <div
  87. class="bamboo-hot2-b4"
  88. :class="{
  89. hide: hotVisible && checkedHotId !== 4
  90. }"
  91. >
  92. <img
  93. src="./images/bamboo4.png"
  94. @load="handleBambooOffset(4)"
  95. >
  96. <div
  97. class="bamboo-hot2__hot"
  98. :class="{
  99. hide: hotVisible
  100. }"
  101. @click="handleHot(4)"
  102. >
  103. <p>楠竹</p>
  104. </div>
  105. </div>
  106. <div
  107. class="bamboo-hot2-b7"
  108. :class="{
  109. hide: hotVisible && checkedHotId !== 7
  110. }"
  111. >
  112. <img
  113. src="./images/bamboo7.png"
  114. @load="handleBambooOffset(7)"
  115. >
  116. </div>
  117. <div
  118. class="bamboo-hot2__hot b7"
  119. :class="{
  120. hide: hotVisible
  121. }"
  122. @click="handleHot(7)"
  123. >
  124. <p>湘妃竹</p>
  125. </div>
  126. <div
  127. class="bamboo-hot2-b8"
  128. :class="{
  129. hide: hotVisible && checkedHotId !== 8
  130. }"
  131. >
  132. <img
  133. src="./images/bamboo8.png"
  134. @load="handleBambooOffset(8)"
  135. >
  136. <div
  137. class="bamboo-hot2__hot"
  138. :class="{
  139. hide: hotVisible
  140. }"
  141. @click="handleHot(8)"
  142. >
  143. <p>单竹</p>
  144. </div>
  145. </div>
  146. <div
  147. ref="bambooWrapBg"
  148. class="bamboo-hot2-bg-wrap"
  149. >
  150. <img
  151. v-if="bgImgLoaded"
  152. class="bamboo-hot2__grass"
  153. :class="{
  154. hide: hotVisible
  155. }"
  156. src="./images/grass.png"
  157. >
  158. <img
  159. class="bamboo-hot2__bg"
  160. src="./images/bg.png"
  161. :style="{
  162. filter: hotVisible ? 'saturate(1.3) brightness(0.95)' : 'none'
  163. }"
  164. @load="bgImgLoaded = true"
  165. >
  166. </div>
  167. </div>
  168. <div class="system-btns">
  169. <BtnBack @click="goBack" />
  170. <OperationTip
  171. id="operationH"
  172. class="operation-h"
  173. text=""
  174. direction="h"
  175. />
  176. </div>
  177. </div>
  178. </template>
  179. <script setup>
  180. import { ref, watch, onBeforeUnmount } from 'vue'
  181. import { useRouter } from 'vue-router'
  182. import useSizeAdapt from "@/useFunctions/useSizeAdapt"
  183. let itemScrollMap = {
  184. 1: 0,
  185. 2: 0,
  186. 3: 0,
  187. 4: 450,
  188. 7: 0,
  189. 8: 1048,
  190. }
  191. const ITEM_INFO_MAP = {
  192. 1: {
  193. label: '水竹',
  194. info: '篷耳小,形状为卵形或长椭圆形。锋舌边缘生有短白纤毛。筝片直立,呈三角形至狭长三角形。'
  195. },
  196. 2: {
  197. label: '紫竹',
  198. info: '紫竹幼竿绿色,覆盖细柔毛和白粉,幕环有毛,籍鞘背面红褐色或绿色加深。叶片小而薄,窄披针形。'
  199. },
  200. 3: {
  201. label: '梅鹿竹',
  202. info: '梅鹿竹斑纹相连,圆形,外轮廓深色,斑心发白。竹地上有兽斑状斑痕,酷似梅花鹿的花纹。'
  203. },
  204. 4: {
  205. label: '楠竹',
  206. info: '单轴散生型常绿乔木状竹类植物,呈直立状,竹叶深绿,呈披针形'
  207. },
  208. 7: {
  209. label: '湘妃竹',
  210. info: '中小型竹,竿环及箨(tuò)环隆起,竿箨黄褐色,有黑褐色斑点,箨叶三角形或带形'
  211. },
  212. 8: {
  213. label: '单竹',
  214. info: '竹质细腻,纤维韧性极强,可制成薄如蝉翼的竹篾丝,编织成绸似、绢似的精美竹编工艺品。'
  215. },
  216. }
  217. const bgImgLoaded = ref(false)
  218. const {
  219. windowSizeInCssForRef,
  220. windowSizeWhenDesignForRef,
  221. } = useSizeAdapt()
  222. const router = useRouter()
  223. const bambooWrap = ref()
  224. const bambooWrapBg = ref()
  225. const hotVisible = ref(false)
  226. const checkedHotId = ref(0)
  227. const handleHot = (id) => {
  228. checkedHotId.value = id
  229. hotVisible.value = true
  230. cancelAnimationFrame(animationFrameId.value)
  231. bambooWrap.value.scrollTo({
  232. left: itemScrollMap[id],
  233. behavior: 'smooth'
  234. })
  235. translateX.value = itemScrollMap[id]
  236. }
  237. const handleBambooOffset = (target) => {
  238. let temp = 0
  239. if ([7, 8].includes(target)) {
  240. if (target === 7) {
  241. temp = window.innerWidth * 0.2
  242. } else {
  243. temp = window.innerWidth * 0.2
  244. }
  245. itemScrollMap[target] = maxTranslateXLength - temp
  246. return
  247. }
  248. const offset = window.innerWidth / 6
  249. const left = document.getElementsByClassName(`bamboo-hot2-b${target}`)?.[0].getBoundingClientRect().left
  250. switch (target) {
  251. case 3:
  252. temp = window.innerWidth / 3
  253. break
  254. case 4:
  255. temp = -(window.innerWidth * 0.25)
  256. break
  257. }
  258. itemScrollMap[target] = left - offset + temp
  259. }
  260. // 动画帧相关
  261. const lastAnimationTimeStamp = ref(0)
  262. const animationFrameId = ref(0)
  263. const moveSpeed = ref(0)
  264. const translateX = ref(0)
  265. const match = windowSizeInCssForRef.value.match(/\d+/)
  266. const maxTranslateXLength = (window.innerWidth * 3 - 150) / Number(windowSizeWhenDesignForRef.value) * parseInt(match[0], 10)
  267. const lastMoveEventTimeStamp = ref(0)
  268. const isMouseDown = ref(false)
  269. const isMove = ref(false)
  270. const lastTouchPos = ref(0)
  271. watch([bambooWrapBg, bgImgLoaded], () => {
  272. if (!bgImgLoaded.value) return
  273. animationFrameId.value = requestAnimationFrame(animationFrameTask)
  274. })
  275. const animationFrameTask = () => {
  276. const timeStamp = Date.now()
  277. const timeElapsed = timeStamp - lastAnimationTimeStamp.value
  278. // 速度减慢
  279. if (moveSpeed.value > 0) {
  280. moveSpeed.value -= 0.002 * timeElapsed
  281. if (moveSpeed.value < 0) {
  282. moveSpeed.value = 0
  283. }
  284. } else if (moveSpeed.value < 0) {
  285. moveSpeed.value += 0.002 * timeElapsed
  286. if (moveSpeed.value > 0) {
  287. moveSpeed.value = 0
  288. }
  289. }
  290. // 根据速度更新距离
  291. translateX.value += moveSpeed.value * timeElapsed
  292. if (translateX.value < 0) {
  293. translateX.value = 0
  294. } else if (translateX.value > maxTranslateXLength) {
  295. translateX.value = maxTranslateXLength
  296. moveSpeed.value = 0
  297. }
  298. bambooWrap.value?.scrollTo({
  299. left: translateX.value,
  300. behavior: 'instant'
  301. })
  302. lastAnimationTimeStamp.value = timeStamp
  303. animationFrameId.value = requestAnimationFrame(animationFrameTask)
  304. }
  305. const handleTouchstart = (e) => {
  306. if (hotVisible.value) return
  307. isMouseDown.value = true
  308. moveSpeed.value = 0
  309. lastMoveEventTimeStamp.value = 0
  310. lastAnimationTimeStamp.value = Date.now()
  311. lastTouchPos.value = e.changedTouches[0].clientX
  312. }
  313. const handleTouchmove = (e) => {
  314. e.preventDefault()
  315. if (hotVisible.value || !isMouseDown.value || !e.changedTouches.length) return
  316. if (
  317. lastMoveEventTimeStamp.value &&
  318. e.timeStamp - lastMoveEventTimeStamp.value > 1
  319. ) {
  320. // 更新speed
  321. isMove.value = true
  322. const currentMoveSpeed =
  323. (-(e.changedTouches[0].clientX - lastTouchPos.value) /
  324. (e.timeStamp - lastMoveEventTimeStamp.value)) *
  325. 1.5
  326. moveSpeed.value = moveSpeed.value * 0.9 + currentMoveSpeed * 0.1
  327. lastTouchPos.value = e.changedTouches[0].clientX
  328. }
  329. lastMoveEventTimeStamp.value = e.timeStamp
  330. }
  331. const handleTouchend = () => {
  332. if (hotVisible.value) return
  333. isMouseDown.value = false
  334. setTimeout(() => {
  335. isMove.value = false
  336. })
  337. }
  338. const goBack = () => {
  339. if (hotVisible.value) {
  340. hotVisible.value = false
  341. checkedHotId.value = 0
  342. animationFrameId.value = requestAnimationFrame(animationFrameTask)
  343. return
  344. }
  345. router.replace({
  346. name: 'MoreContent',
  347. query: {
  348. anchorIdx: 0,
  349. }
  350. })
  351. }
  352. onBeforeUnmount(() => {
  353. cancelAnimationFrame(animationFrameId.value)
  354. })
  355. </script>
  356. <style lang="less" scoped>
  357. ::-webkit-scrollbar {
  358. display: none;
  359. }
  360. img {
  361. pointer-events: none;
  362. }
  363. .hide {
  364. opacity: 0 !important;
  365. animation: none !important;
  366. }
  367. [class^="bamboo-hot2-b"] {
  368. position: absolute;
  369. top: 0;
  370. height: 100%;
  371. z-index: 3;
  372. img {
  373. height: 100%;
  374. }
  375. }
  376. .bamboo-hot2-hot {
  377. display: flex;
  378. position: fixed;
  379. top: 50%;
  380. right: calc(70 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  381. transform: translateY(-50%);
  382. color: white;
  383. font-family: KaiTi;
  384. writing-mode: vertical-rl;
  385. white-space: nowrap;
  386. z-index: 999;
  387. &.left {
  388. right: unset;
  389. left: calc(70 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  390. writing-mode: vertical-lr;
  391. .bamboo-hot2-hot__title::before {
  392. right: unset;
  393. left: calc(-5 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  394. }
  395. }
  396. &__title {
  397. position: relative;
  398. margin-bottom: calc(90 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  399. font-weight: bold;
  400. letter-spacing: 5px;
  401. font-size: calc(38 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  402. &::before {
  403. content: '';
  404. position: absolute;
  405. top: calc(-2 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  406. right: calc(25 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  407. width: calc(15 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  408. height: calc(15 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  409. border-radius: 50%;
  410. border: 1px solid #F8DD86;
  411. z-index: -1;
  412. }
  413. }
  414. &__inner {
  415. letter-spacing: 2px;
  416. white-space: pre-wrap;
  417. line-height: calc(40 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  418. font-size: calc(20 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  419. height: calc(400 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  420. }
  421. }
  422. .bamboo-hot2 {
  423. position: relative;
  424. width: 100%;
  425. height: 100%;
  426. perspective: calc(100 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  427. transform-style: preserve-3d;
  428. overflow-x: auto;
  429. > * {
  430. opacity: 1;
  431. transition: all linear .2s;
  432. }
  433. &__hot {
  434. display: flex;
  435. align-items: center;
  436. justify-content: center;
  437. position: absolute;
  438. color: white;
  439. font-size: 12px;
  440. font-family: KaiTi;
  441. writing-mode: vertical-rl;
  442. animation: breathing linear 2s infinite;
  443. &::before {
  444. content: '';
  445. display: block;
  446. margin-bottom: calc(5 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  447. width: 20px;
  448. height: 20px;
  449. background: url('./images/hot.png') no-repeat center / contain;
  450. }
  451. }
  452. &-b1 {
  453. left: calc(50 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  454. transform: translateZ(calc(20 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'))) scale(0.8);
  455. .bamboo-hot2__hot {
  456. top: calc(340 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  457. right: calc(40 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  458. }
  459. }
  460. &-b2 {
  461. left: calc(320 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  462. transform: translateZ(calc(10 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef')));
  463. z-index: 2;
  464. }
  465. .bamboo-hot2__hot.b2 {
  466. top: calc(180 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  467. left: calc(370 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  468. transform: translateZ(calc(10 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef')));
  469. z-index: 4;
  470. }
  471. &-b3 {
  472. left: calc(210 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  473. transform: translateZ(calc(20 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'))) scale(0.8);
  474. }
  475. .bamboo-hot2__hot.b3 {
  476. top: calc(310 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  477. left: calc(464 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  478. transform: translateZ(calc(20 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef')));
  479. z-index: 4;
  480. }
  481. &-b4 {
  482. left: calc(780 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  483. transform: translateZ(calc(15 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'))) scale(0.85);
  484. .bamboo-hot2__hot {
  485. top: calc(220 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  486. right: calc(14 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  487. }
  488. }
  489. &-b7 {
  490. left: calc(950 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  491. transform: translateZ(calc(10 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef')));
  492. z-index: 2;
  493. }
  494. .bamboo-hot2__hot.b7 {
  495. top: calc(160 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  496. left: calc(1230 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  497. transform: translateZ(calc(10 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef')));
  498. z-index: 4;
  499. }
  500. &-b8 {
  501. left: calc(1080 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  502. transform: translateZ(calc(25 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'))) scale(0.8);
  503. .bamboo-hot2__hot {
  504. top: calc(320 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  505. right: calc(110 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  506. }
  507. }
  508. &.wrap-hide {
  509. &::before,
  510. &::after {
  511. opacity: 0;
  512. }
  513. }
  514. &::before {
  515. content: '';
  516. position: absolute;
  517. top: 0;
  518. left: 0;
  519. width: calc(457 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  520. height: calc(330 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  521. background: url('./images/leaf.png') no-repeat center / contain;
  522. opacity: 1;
  523. transition: opacity linear .2s;
  524. z-index: 3;
  525. }
  526. &-bg-wrap {
  527. width: fit-content;
  528. height: 100%;
  529. position: relative;
  530. z-index: 1;
  531. }
  532. &__grass {
  533. position: absolute;
  534. left: 0;
  535. bottom: 0;
  536. width: 100%;
  537. z-index: 1;
  538. }
  539. &__bg {
  540. height: 100%;
  541. }
  542. }
  543. .system-btns {
  544. width: 100%;
  545. padding: 0 calc(20 / v-bind(windowSizeWhenDesignForRef) * v-bind(windowSizeInCssForRef));
  546. display: flex;
  547. // flex-direction: column;
  548. justify-content: flex-end;
  549. position: absolute;
  550. bottom: calc(60 /v-bind(windowSizeWhenDesignForRef) * v-bind(windowSizeInCssForRef));
  551. z-index: 2;
  552. .operation-h {
  553. width: calc(36 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
  554. transition: opacity 0.5s ease-in-out;
  555. }
  556. }
  557. @keyframes breathing {
  558. 0% {
  559. opacity: 1;
  560. }
  561. 50% {
  562. opacity: 0.3;
  563. }
  564. 100% {
  565. opacity: 1;
  566. }
  567. }
  568. </style>