tremble 2 년 전
부모
커밋
63732cd47d
46개의 변경된 파일1299개의 추가작업 그리고 175개의 파일을 삭제
  1. 1 0
      package.json
  2. BIN
      public/model/manshouxing.png
  3. BIN
      public/model/reticle-animation.png
  4. BIN
      public/model/shouxinger.png
  5. BIN
      public/model/shouxingniu.png
  6. BIN
      public/model/zimukou.png
  7. 154 66
      src/App.scss
  8. 488 81
      src/App.tsx
  9. BIN
      src/assets/images/huaxueqingliao.jpg
  10. BIN
      src/assets/images/huiqing.jpg
  11. BIN
      src/assets/images/ico_back.png
  12. BIN
      src/assets/images/ico_cancel.png
  13. BIN
      src/assets/images/ico_change_active.png
  14. BIN
      src/assets/images/ico_change_normal.png
  15. BIN
      src/assets/images/ico_history_active.png
  16. BIN
      src/assets/images/ico_history_normal.png
  17. BIN
      src/assets/images/ico_more_normal.png
  18. BIN
      src/assets/images/ico_painting_active.png
  19. BIN
      src/assets/images/ico_painting_normal.png
  20. BIN
      src/assets/images/ico_up.png
  21. BIN
      src/assets/images/ico_visual_active.png
  22. BIN
      src/assets/images/ico_visual_normal.png
  23. BIN
      src/assets/images/line.png
  24. 59 5
      src/assets/images/loadImg.js
  25. BIN
      src/assets/images/pingdengqing.jpg
  26. BIN
      src/assets/images/shiziqing.jpg
  27. BIN
      src/assets/images/sumaliqing.jpg
  28. BIN
      src/assets/images/xuliezhen.jpg
  29. BIN
      src/assets/images/xuliezhen.png
  30. BIN
      src/assets/images/xuliezhen1.png
  31. BIN
      src/assets/images/xuliezhen2.png
  32. BIN
      src/assets/images/xuliezhen222.png
  33. BIN
      src/assets/images/zheliao.jpg
  34. BIN
      src/assets/images/zhumingqing.jpg
  35. 76 0
      src/components/LongImageSequence/index copy.tsx
  36. 40 0
      src/components/LongImageSequence/index.module.scss
  37. 126 0
      src/components/LongImageSequence/index.tsx
  38. 31 0
      src/components/LongImageSequence/scale.tsx
  39. 12 0
      src/components/Slider.css
  40. 114 0
      src/components/Slider.tsx
  41. 2 2
      src/components/Toast/index.module.scss
  42. 9 17
      src/components/huajuan.tsx
  43. 12 0
      src/components/index.module.scss
  44. 32 3
      src/index.css
  45. 131 1
      src/utils/index.tsx
  46. 12 0
      yarn.lock

+ 1 - 0
package.json

@@ -15,6 +15,7 @@
     "react-dom": "^18.2.0",
     "react-scripts": "5.0.1",
     "sass": "^1.60.0",
+    "swiper": "^9.2.4",
     "three": "^0.151.1",
     "typescript": "^4.9.5",
     "web-vitals": "^2.1.4"

BIN
public/model/manshouxing.png


BIN
public/model/reticle-animation.png


BIN
public/model/shouxinger.png


BIN
public/model/shouxingniu.png


BIN
public/model/zimukou.png


+ 154 - 66
src/App.scss

@@ -3,12 +3,15 @@
   height: 100%;
   overflow: hidden;
   background-color: #3c454c;
+  color: #a2b4af;
   position: relative;
   .logo {
     position: absolute;
-    left: 20px;
+    right: 20px;
     top: 20px;
     width: 40px;
+    z-index: 999;
+    pointer-events: none;
   }
   .loadbox {
     height: 100%;
@@ -47,7 +50,7 @@
           transform: translate(-50%, -50%);
           font-size: 14px;
           font-weight: bold;
-          color: #000;
+          color: #a2b4af;
         }
       }
       .model {
@@ -59,19 +62,19 @@
   .mask {
     position: absolute;
     width: 100%;
-    height: 96%;
-    bottom: -40%;
+    height: 100%;
+    bottom: 0%;
     background: #35393e;
     border: 1px solid #3a4a69;
-    border-radius: 40px 40px 0 0;
     z-index: 10;
     transition: 0.3s ease bottom;
     left: 0;
+    padding-top: 8%;
+    overflow-y: auto;
     .info {
       color: #a2b4af;
-      padding: 10% 10%;
+      padding: 4% 10%;
       width: 100%;
-      height: 60%;
       box-sizing: border-box;
       > h3 {
         font-size: 24px;
@@ -109,79 +112,164 @@
           }
         }
       }
+      .yanlian{
+        display: flex;
+        justify-content: space-between;
+        margin-top: 10px;
+        >ul{
+          width: 30%;
+          >li{
+            text-align: center;
+            margin-bottom: 10px;
+            background-color: #2A2E32;
+            border-radius: 2px;
+            line-height: 34px;
+            width: 100%;
+            >span{
+              width: 100%;
+              display: inline-block;
+            }
+            &.active{
+              background-color: #7C8986;
+              color: #35393E;
+            }
+          }
+        }
+
+        >div{
+          width: 60%;
+          >img{
+            width: 100%;
+          }
+          .ttp{
+            margin-top: 20px;
+          }
+          >p{
+            font-size: 16px;
+          }
+        }
+      }
     }
 
-    .shoula {
-      position: absolute;
-      right: 11%;
-      top: 3%;
-      width: 44px;
-      z-index: 99;
-      > img {
+  }
+
+  .history{
+    padding: 80px 20px 30px;
+    >ul{
+      width: 100%;
+      >li{
         width: 100%;
+        padding: 20px 10px;
+        background: rgba(32,39,49,0.9);
+        border-radius: 20px;
+        backdrop-filter: blur(10px);
+        margin-bottom: 20px;
+        >h3{
+          font-size: 24px;
+          text-align: center;
+        }
+        >div{
+          margin-top: 10px;
+          font-size: 18px;
+          >p{
+            line-height: 1.5;
+          }
+        }
       }
     }
   }
 
+  
+  .shoula {
+    position: fixed;
+    right: 20px;
+    bottom: 40px;
+    width: 48px;
+    z-index: 99;
+    > img {
+      width: 100%;
+    }
+  }
+  .more_btn {
+    position: fixed;
+    bottom: 6%;
+    left: 50%;
+    transform: translateX(-50%);
+    text-align: center;
+    > img {
+      width: 50px;
+      height: 50px;
+    }
+    > span {
+      display: block;
+    }
+  }
+
   .btnlist {
     position: absolute;
-    bottom: 24%;
+    bottom: 50px;
     z-index: 9;
-    left: 50%;
-    transform: translateX(-50%);
-    width: 100%;
-    > ul {
-      margin: 0 auto;
-      width: 90%;
-      display: flex;
-      justify-content: space-around;
-      > li {
-        width: 18%;
+    right: 20px;
+    width: 48px;
+    > li {
+      width: 100%;
+      text-align: center;
+      margin-bottom: 10px;
+      .img {
+        width: 100%;
+        position: relative;
+        font-size: 0;
         text-align: center;
-        .img {
+        > img {
           width: 100%;
-          position: relative;
           font-size: 0;
-          > img {
-            width: 100%;
-            border-radius: 50%;
-            background-color: #fff;
-            font-size: 0;
-            overflow: hidden;
-            border: 3px solid #A2B4AF;
-          }
-          .imgmask {
-            display: inline-block;
-            background: rgba($color: #000000, $alpha: 0.5);
-            z-index: 9;
-            position: absolute;
-            left: 0%;
-            top: 0%;
-            font-size: 0;
-            overflow: hidden;
-            border-radius: 50%;
-            width: 100%;
-            height: 100%;
-          }
-        }
-        > span {
-          color: #708b97;
-          display: inline-block;
-          margin-top: 8px;
-        }
-        &.active {
-          .img {
-            >img{
-            }
-            .imgmask {
-              display: none;
-            }
-          }
-          > span {
-            color: #a2b4af;
-          }
         }
       }
+      > span {
+        display: inline-block;
+        margin-top: 8px;
+      }
+      &.active {
+      
+      }
+    }
+  }
+
+  .jianshanglist{
+    position: absolute;
+    bottom: 90px;
+    z-index: 999;
+    left: 50%;
+    transform: translateX(-50%);
+    >ul{
+      display: flex;
+      >li{
+        margin: 0 10px;
+        border-radius: 50%;
+        border: 4px solid #fff;
+        width: 48px;
+        height: 48px;
+      }
+    }
+  }
+
+  .juanzhou{
+    position: fixed;
+    bottom: 0;
+    top: 0;
+    left: 0;
+    right: 0;
+    width: 100%;
+    height: 100%;
+    z-index: 999;
+    backdrop-filter: blur(15px);
+    display: flex;
+    align-items: center;
+    .j-close{
+      position: absolute;
+      bottom: 12%;
+      left: 50%;
+      transform: translateX(-50%);
     }
   }
 
@@ -197,6 +285,6 @@
   }
   #webgl {
     width: 100%;
-    height: 77%;
+    height: 100%;
   }
 }

+ 488 - 81
src/App.tsx

@@ -18,6 +18,12 @@ import Huajuan from "./components/huajuan";
 import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader.js";
 import { MTLLoader } from "three/examples/jsm/loaders/MTLLoader.js";
 
+import { Hotspot } from "./utils/index";
+
+
+import Toast from "./components/Toast";
+
+
 import {
   loading_bg,
   img_culture,
@@ -25,7 +31,9 @@ import {
   label_title,
   logo,
   ico_up,
-  ico_down,
+  lxuliezhen,
+  xuliezhen1,
+  xuliezhen2,
   img_manshouxing,
   img_shouxinger,
   img_shouxingniu,
@@ -34,12 +42,29 @@ import {
   img_texture,
   ico_change_active,
   ico_change_normal,
+  ico_history_active,
+  ico_history_normal,
+  ico_painting_active,
+  ico_painting_normal,
+  ico_visual_active,
+  ico_visual_normal,
   line_boat,
   line_friends,
   line_mount,
-  imgtext
+  imgtext,
+  ico_back,
+  huaxueqingliao,
+  zhumingqing,
+  zheliao,
+  sumaliqing,
+  shiziqing,
+  huiqing,
+  pingdengqing,
+  ico_cancel
+
+
 } from "./assets/images/loadImg.js";
-import Toast from "./components/Toast/index";
+import Scale from "./components/LongImageSequence/scale";
 
 
 function App() {
@@ -62,7 +87,11 @@ function App() {
 
 
 
+
   let container = null, containerW = 0, containerH = 0,
+
+    hotspots = useRef<any[]>([]),
+
     emissiveTexture = useRef<THREE.Texture>(),
     scene: THREE.Scene,
     startMouse = useRef<THREE.Vector2>(new THREE.Vector2(0, 0)),
@@ -71,6 +100,12 @@ function App() {
     showInfoRef = useRef<Boolean>(false),
     isOpenPingGai = useRef<Boolean>(false),
 
+    canHandleHotspot = useRef<Boolean>(false),
+
+    canAction = useRef<Boolean>(false),
+
+
+
     camera = useRef<THREE.Camera | THREE.PerspectiveCamera>(),
     rafId: number,
     textrueLoader = useRef<THREE.TextureLoader>(new THREE.TextureLoader()),
@@ -102,43 +137,246 @@ function App() {
     }
   ]
 
+
+
+  const colors = [
+
+    {
+      id: 1,
+      key: '苏麻离青',
+      age: '元代后期-明永宣时期',
+      img: sumaliqing,
+      textrue: '发色浓重青翠,有“铁锈斑痕”,“锡光”'
+    },
+    {
+      id: 2,
+      key: '平等青',
+      img: pingdengqing,
+      age: '明成弘时期',
+      textrue: '发色灰蓝淡雅、青亮、稳定'
+    },
+    {
+      id: 3,
+      img: huiqing,
+      key: '回青',
+      age: '明嘉万时期',
+      textrue: '发色清幽泛紫,单独使用浑散不收,多于石子青混合使用'
+    },
+    {
+      id: 4,
+      img: zheliao,
+      key: '浙料',
+      age: '明万历中期-清代',
+      textrue: '发色青翠艳丽明亮,无铁锈斑痕'
+    },
+    {
+      id: 5,
+      img: shiziqing,
+      key: '石子青',
+      age: '明清二代民窑使用',
+      textrue: '发色蓝中带黑,灰暗,一般与回青混合使用'
+    },
+    {
+      id: 6,
+      img: zhumingqing,
+      key: '珠明青',
+      age: '清康熙时期',
+      textrue: '发色正蓝,明丽纯正,如蓝宝石般明亮'
+    },
+    {
+      id: 7,
+      img: huaxueqingliao,
+      key: '化学青料',
+      age: '现代发明的人工青料',
+      textrue: '发色紫蓝,纯粹,浓艳但轻浮而缺乏附着力,容易飘'
+    },
+  ]
+
+
+  const ageArr = [
+    {
+      id: 1,
+      key: '唐宋',
+      textarea: ['青花瓷创烧于唐,此时制瓷工艺还不够成熟,处于滥觞期,主要用于外销。唐青花的产地可以判定为河南巩窑。',
+        '而宋代人崇尚朴素的审美文化所以青花瓷在宋朝并没有得到较大的发展。',
+        '唐宋时期的青花瓷还处于一个拙朴的状态。']
+    },
+    {
+      id: 2,
+      key: '元',
+      textarea: ['元代,青花瓷的发展迎来了一个小高峰。',
+        '景德镇出产的青花瓷标志着青花制瓷工艺已经达到了一个非常成熟的水准,业界人士将之简称为“元青花”。']
+    },
+    {
+      id: 3,
+      key: '明洪武',
+      textarea: ['景德镇御窑厂正式成立,此时的青花风格仍保留一部分元代遗风。']
+    },
+    {
+      id: 4,
+      key: '明永宣',
+      textarea: ['苏麻离青料技艺的革新,以及永乐皇帝的重视,让永乐青花达到第个小高峰。宣德和永乐时期的青花风格很相似。制造工艺水平极高,可以说,永宣时期的青花是冠绝明代的。']
+    },
+    {
+      id: 5,
+      key: '明成弘',
+      textarea: ['青花瓷的发展在正统、景泰、天顺三朝停滞后又迎来了一个小高潮,青花用料改为平等青,青花发色有了较强的进步。']
+    },
+    {
+      id: 6,
+      key: '明嘉万',
+      textarea: ['明代中晚期,国家开始走向衰落,经济衰退,青花的质量逐渐下降,大不如前。制瓷工艺和青花风格上没什么创新和特色,没什么较大的发展。']
+    },
+    {
+      id: 7,
+      key: '清康熙',
+      textarea: ['到了康熙一朝,由于国家逐渐安定繁荣,青花又快速发展起来,以“五彩青花”为代表使青花瓷发展到了巅峰。']
+    },
+    {
+      id: 8,
+      key: '清雍正',
+      textarea: ['雍正皇帝对瓷器十分重视,派遣年希尧、唐英管理御窑厂,让雍正一朝保持超高的工艺水准。并且雍正时期青花多为仿永宣青花作品,虽少有创新,但其工艺水准之高,绝对不输前朝。']
+    },
+    {
+      id: 9,
+      key: '清乾隆',
+      textarea: ['青花发展至乾隆一朝后,其制瓷工艺已经高度成熟,瓷器生产的数量和质量都达到了顶峰,也有相当数量的仿古瓷器。总体而言,工艺已经登峰造极。']
+    },
+    {
+      id: 10,
+      key: '晚清',
+      textarea: ['督陶官制度被终结,景德镇官窑不再受到中央的重视。',
+        '战乱、政局动荡,使青花彻底地走 向了没落,无论是制瓷工艺还是创新,都处于极低的水准。']
+    },
+    {
+      id: 11,
+      key: '中华民国',
+      textarea: ['民国时期的青花瓷器,又称之为“洋蓝瓷器”,其颜色多发暗蓝,纹饰图案以绘画和诗词相结合为主,但是绘功不高,不太精细。',
+        '民国时期的青花瓷器青料质量有高有低。高质量的青花色泽纯正艳丽,低质量的青花色泽多泛灰,而且有杂质,缺少一种鲜活的美感。']
+    }
+  ]
+
+
   const cpArr = [
     {
       id: 1,
       name: '馒首形',
       img: img_manshouxing,
+      posInModel: new THREE.Vector3(
+        0.009043116204992076,
+        0.22948584032184838,
+        0.015596999822647409
+      ),
+      titleInPoint: new THREE.Vector3(
+        0.1, 0.24948584032184838, 0.05
+      ),
+      title: textrueLoader.current!.load(`model/manshouxing.png`),
       wenli: textrueLoader.current!.load(`model/1-manshouxing.jpg`)
     },
     {
       id: 2,
       name: '兽形耳',
       img: img_shouxinger,
+      posInModel: new THREE.Vector3(
+        0.08457595303316055,
+        0.056032072652909666,
+        0.1059102513639425),
+      titleInPoint: new THREE.Vector3(
+        0.12457595303316055,
+        0.156032072652909666,
+        0.2059102513639425
+      ),
+      title: textrueLoader.current!.load(`model/shouxinger.png`),
       wenli: textrueLoader.current!.load(`model/2-shouxinger.jpg`)
     },
     {
       id: 3,
       name: '子母口',
       img: img_zimukou,
+      posInModel: new THREE.Vector3(
+        -0.09101613983409917,
+        0.16361387703933025,
+        0.0272763910188533
+      ),
+      titleInPoint: new THREE.Vector3(
+        -0.16101613983409917,
+        0.26361387703933025,
+        0.0272763910188533
+      ),
+      title: textrueLoader.current!.load(`model/zimukou.png`),
       wenli: textrueLoader.current!.load(`model/3-zimukou.jpg`)
     },
     {
       id: 4,
       name: '兽形钮',
       img: img_shouxingniu,
+      posInModel: new THREE.Vector3(0, 0.25, 0),
+      titleInPoint: new THREE.Vector3(0, 0.35, 0),
+      title: textrueLoader.current!.load(`model/shouxingniu.png`),
       wenli: textrueLoader.current!.load(`model/4-shouxingniu.jpg`)
     }
   ]
 
+  const buttonArr = [
+    {
+      id: 1,
+      name: '赏析',
+      img: ico_visual_normal,
+      active: ico_visual_active
+    },
+    {
+      id: 2,
+      name: '画卷',
+      img: ico_painting_normal,
+      active: ico_painting_active
+    },
+    {
+      id: 3,
+      name: '历史',
+      img: ico_history_normal,
+      active: ico_history_active
+    },
+    {
+      id: 4,
+      name: '复位',
+      img: ico_change_normal,
+      active: ico_change_active
+    }
+  ]
+
+  const jianshangArr = [
+    {
+      id: 1,
+      color: '#D6D6D6'
+    },
+    {
+      id: 2,
+      color: '#FFF7DB'
+    },
+    {
+      id: 3,
+      color: '#C2B7BF'
+    },
+  ]
 
 
   let [btnactive, setBtnactive] = useState({} as { name: string, id: number, wenli: string })
+
+  let [coloractive, setColoractive] = useState(colors[0] as { key: string, id: number, img: string, textrue: string, age: string })
+
+  let [jianshangColor, setJianshangColor] = useState(jianshangArr[0] as { color: string, id: number })
+
+
+
   let [changeLeft, setchangeLeft] = useState(0)
   let [loaded, setloaded] = useState(0)
 
   let [showInfo, setShowInfo] = useState(false)
+  let [showHistory, setShowHistory] = useState(false)
+  let [showJianShang, setShowJianShang] = useState(false)
+  let [showJuanZhou, setShowJuanZhou] = useState(false)
 
-  let [currentMsg, setcurrentMsg] = useState('')
-  let [isReset, setIsReset] = useState(false)
+  let [currentScale, setCurrentScale] = useState({} as any)
 
 
   let fixinfo = useMemo(() => {
@@ -178,10 +416,9 @@ function App() {
 
   const resetCamera = () => {
     controls.current!.enableZoom = false
-    setIsReset(false)
 
     if (isOpenPingGai.current) {
-      movePinggai(-MOVEPINGGAI)
+      movePinggaiFn(-MOVEPINGGAI)
     }
 
     let tween = new Tween({
@@ -212,7 +449,6 @@ function App() {
       controls.current!.enableZoom = true
       let t1 = setTimeout(() => {
         clearTimeout(t1)
-        setIsReset(true)
       }, 500);
     }).start()
   }
@@ -246,7 +482,7 @@ function App() {
         obj.current.traverse((child) => {
           if (child instanceof THREE.Mesh) {
             if (child.name === PING_GAI) {
-              outlineObj(child)
+              // outlineObj(child)
             }
           }
         })
@@ -281,35 +517,30 @@ function App() {
             obj.current = object
             object.traverse((child) => {
               if (child instanceof THREE.Mesh) {
-                if (child.isMesh) {
-                  if (child instanceof THREE.Mesh) {
-                    console.log('result:', child.name);
-                    if (child.name === PING_GAI) {
-                      outlineObj(child)
-                    }
-                  }
-                  emissiveTexture.current = textrueLoader.current.load("model/shanchuanren.jpg");
-                  let step = 1
-
-                  setInterval(() => {
-                    if (showWenli.current && showWenli.current === child.name) {
-                      if (child.material.emissiveIntensity > 1.2) {
-                        step = -1
-                      }
-                      if (child.material.emissiveIntensity < 0) {
-                        step = 1
-                      }
-                      child.material.emissiveIntensity += 0.03 * step;
+                //   if (child.name === PING_GAI) {
+                //     outlineObj(child)
+                //   }
+                emissiveTexture.current = textrueLoader.current.load("model/shanchuanren.jpg");
+                let step = 1
+
+                setInterval(() => {
+                  if (showWenli.current && showWenli.current === child.name) {
+                    if (child.material.emissiveIntensity > 1.2) {
+                      step = -1
                     }
-                    else {
-                      child.material.emissiveMap = emissiveTexture.current;
-                      child.material.emissiveIntensity = 0;
+                    if (child.material.emissiveIntensity < 0) {
+                      step = 1
                     }
-                  }, 50);
+                    child.material.emissiveIntensity += 0.03 * step;
+                  }
+                  else {
+                    child.material.emissiveMap = emissiveTexture.current;
+                    child.material.emissiveIntensity = 0;
+                  }
+                }, 50);
 
-                  child.material.emissive = new THREE.Color(0xffffff);
-                  child.material.dispose();
-                }
+                child.material.emissive = new THREE.Color(0xffffff);
+                child.material.dispose();
               }
             });
 
@@ -335,6 +566,7 @@ function App() {
 
   }
 
+
   const addHelper = () => {
     const box = new THREE.BoxHelper(obj.current!, 0xffff00);
     scene.add(box);
@@ -368,8 +600,8 @@ function App() {
       // [-0.341,0.2594,0.342],
       // [-0.486,-0.2250,-0.347],
 
-      
- 
+
+
       // [0.641,0.1994,-0.442],
       // [-0.486,-0.2250,0.347],
       // [-0.2,-0.3,0.4],
@@ -408,7 +640,15 @@ function App() {
           controls.current!.enableZoom = true
           controls.current!.autoRotate = true;
           controls.current!.autoRotateSpeed = 1;
-          setIsReset(true)
+          Hotspot.camera = camera.current
+          Hotspot.scene = scene
+          cpArr.forEach(item => {
+            let hotspot = new Hotspot();
+            hotspot.initLabel(item)
+            hotspots.current.push(hotspot)
+          })
+
+          canAction.current = true
         }, 1e3);
       }
       )).start()
@@ -420,7 +660,11 @@ function App() {
     rafId = requestAnimationFrame(animate);
     controls.current?.update();
     renderer.render(scene!, camera.current!);
-
+    hotspots.current.forEach(item => {
+      item.reticleAnim && item.reticleAnim.lookAt(camera.current!.position)
+      item.nameMesh && item.nameMesh.lookAt(camera.current!.position)
+      item.textureAnimator && item.textureAnimator.update(1.5e1)
+    })
     update();
     if (composer) {
       composer.render()
@@ -457,12 +701,27 @@ function App() {
     composer.addPass(effectFXAA)
   }
 
-  const movePinggai = (position: number, speed: number = 5e2) => {
+  const toggleHotspots = (tag: Boolean) => {
+    if (canHandleHotspot.current) {
+      hotspots.current.forEach(item => {
+        tag ? item.show() : item.hide()
+      })
+    }
+  }
+
+  const movePinggaiFn = (position: number, speed: number = 5e2) => {
+    if (!canAction.current) return
     let pinggai_obj = obj.current!.getObjectByName(PING_GAI)
 
+    if (!isOpenPingGai.current) {
+      toggleHotspots(false)
+    }
     let complete = () => {
-      isOpenPingGai.current = !isOpenPingGai.current
       OpeningPingGai = false
+      if (isOpenPingGai.current) {
+        toggleHotspots(true)
+      }
+      isOpenPingGai.current = !isOpenPingGai.current
     }
     new Tween(pinggai_obj!.position).to({
       x: pinggai_obj!.position.x,
@@ -474,19 +733,28 @@ function App() {
 
   const pingGaiUpOrDown = () => {
     if (OpeningPingGai) return
-    console.log('result:', isOpenPingGai.current);
-    isOpenPingGai.current ? movePinggai(-MOVEPINGGAI) : movePinggai(MOVEPINGGAI)
+    isOpenPingGai.current ? movePinggaiFn(-MOVEPINGGAI) : movePinggaiFn(MOVEPINGGAI)
   }
 
   const onMouseClickOrTouchEnd = (event: TouchEvent | MouseEvent) => {
     // 获取 raycaster 和所有模型相交的数组,其中的元素按照距离排序,越近的越靠前
     var intersects = getIntersects(event);
     if (intersects) {
-      // 获取选中最近的 Mesh 对象
-      if (intersects!.length !== 0 && intersects![0].object instanceof THREE.Mesh) {
-        let selectObject = intersects![0].object;
-        if (selectObject.name === PING_GAI) {
-          pingGaiUpOrDown();
+      if (intersects!.length !== 0) {
+        //过滤Line模型
+        let iiis = intersects.filter(item => !(item.object instanceof THREE.Line))
+        if (iiis.length > 0) {
+          let selectObject = iiis![0].object;
+          // 获取选中最近的 Mesh 对象
+          if (selectObject instanceof THREE.Mesh) {
+            if (canHandleHotspot.current) {
+              let current = cpArr.find(item => item.name === selectObject.name)
+              current && handleBtn(current)
+            }
+            if (selectObject.name === PING_GAI) {
+              pingGaiUpOrDown();
+            }
+          }
         }
       }
     }
@@ -590,19 +858,55 @@ function App() {
   }
 
 
-  const toggleInfo = (event: any) => {
+  const closeAll = (event: any) => {
     event.preventDefault()
-    setShowInfo(!showInfo)
+    handleBtnReset(event)
+    setShowHistory(false)
+    setShowInfo(false)
+    setShowJianShang(false)
+    setShowJuanZhou(false)
   }
-  const handleBtn = (event: any, item: any) => {
-    console.log('result:', item);
+
+
+
+  const handleBtn = (item: any) => {
+    setBtnactive((prev) => {
+      if (prev.id === item.id) {
+        return {}
+      } else {
+        return item
+      }
+    })
+
+
+  }
+
+  const handleRightBtn = (event: any, item: any) => {
     event.preventDefault()
-    if (item.id !== btnactive.id) {
-      setBtnactive(item)
+    switch (item.id) {
+      case 1:
+        setShowJianShang(true)
+        handleBtnReset(event)
+        break;
+
+      case 2:
+        setShowJuanZhou(true)
+        break;
+
+      case 3:
+        setShowHistory(true)
+        break;
+
+      case 4:
+        handleBtnReset(event)
+        break;
+
+      default:
+        break;
     }
-
   }
 
+
   const handleBtnReset = (event: any) => {
     event.preventDefault()
     resetCamera()
@@ -623,9 +927,9 @@ function App() {
   }, [])
 
 
+
   useEffect(() => {
     if (btnactive.wenli) {
-
       let pingshen = obj.current?.getObjectByName(PING_SHEN)
       let pinggai = obj.current?.getObjectByName(PING_GAI)
 
@@ -633,27 +937,42 @@ function App() {
       wlmesh.material.emissiveMap = btnactive.wenli;
       showWenli.current = wlmesh.name
       wlmesh.material.dispose();
+    } else {
+      showWenli.current = ''
     }
   }, [btnactive])
 
+
+
+
   useEffect(() => {
-    showInfoRef.current = !currentMsg
-    if (currentMsg) {
+    if (currentScale.id === 2) {
       const timer = setTimeout(() => {
         clearTimeout(timer);
-        setcurrentMsg('');
+        setCurrentScale({});
       }, 3000);
       return () => {
         clearTimeout(timer);
       };
     }
-  }, [currentMsg]);
+  }, [currentScale]);
 
+  useEffect(() => {
+    canHandleHotspot.current = true
+    toggleHotspots(!showJianShang)
+    showWenli.current = ''
+    canHandleHotspot.current = !showJianShang
+  }, [showJianShang]);
 
+  
 
 
   return (
-    <div className="App" style={{ background: `#3c454c url(${loading_bg}) no-repeat 50%/cover` }}>
+    <div className="App" style={{ background: showJianShang ? `${jianshangColor.color}` : `#3c454c url(${loading_bg}) no-repeat 50%/cover` }}>
+
+      {/* 为了触发jianshangColor.color的变化 */}
+      <i style={{ opacity: 0, pointerEvents: 'none' }} >{jianshangColor.color}</i>
+
       {loaded !== 100 && <div className='loadbox' style={{ background: `#3c454c url(${loading_bg}) no-repeat 50%/cover` }}>
         <div className="loadShow">
           <div className="title">
@@ -671,7 +990,7 @@ function App() {
 
       <img src={logo} alt='img' className='logo' />
 
-      <div className="mask" style={{ bottom: showInfo ? 0 : '-73%' }}>
+      <div className="mask" style={{ bottom: showInfo ? 0 : '-101%' }}>
         <div className="info">
           <h3>带盖青花瓷坛</h3>
           <ul>
@@ -690,23 +1009,92 @@ function App() {
           </ul>
         </div>
 
-        <Huajuan
-          handleClick={(item: any) => { setcurrentMsg(item.msg) }}
-          onChange={(temp: any) => { setchangeLeft(temp) }}
-          imgs={{
-            img_juanzhou, img_texture, line_boat,
-            line_friends,
-            line_mount
-          }}
-          left={changeLeft}
-        />
-
-        <div onTouchEnd={toggleInfo} className="shoula">
-          <img src={showInfo ? ico_down : ico_up} alt='img' className='logo' />
+        <div className="info">
+          <h3>青花的七种青料</h3>
+          <div className='yanlian'>
+            <ul>
+              {
+                colors.map(item => {
+                  return (
+                    <li className={`${coloractive.id === item.id ? 'active' : ''}`} onTouchEnd={() => { setColoractive(item) }} key={item.key}>
+                      <span>{item.key}</span>
+                    </li>
+                  )
+                })
+              }
+            </ul>
+
+            <div>
+              <img src={coloractive.img} alt='img' />
+              <p className='ttp'>主要使用时期:</p>
+              <p>{coloractive.age}</p>
+              <p className='ttp'>发色特征:</p>
+              <p>{coloractive.textrue}</p>
+            </div>
+          </div>
         </div>
+
+
       </div>
 
-      <div className='btnlist'>
+      {/* 历史 */}
+      <div className="mask history" style={{ bottom: showHistory ? 0 : '-101%' }}>
+        <ul>
+          {
+            ageArr.map(item => {
+              return (
+                <li key={item.id}>
+                  <h3>{item.key}</h3>
+                  <div>
+                    {
+                      item.textarea.map(sub => {
+                        return <p key={sub}>{sub}</p>
+                      })
+                    }
+                  </div>
+                </li>
+              )
+            })
+          }
+        </ul>
+      </div>
+
+
+
+      {!showJianShang && (
+        <>
+          <div className='more_btn' onTouchEnd={() => setShowInfo(true)}>
+            <img src={ico_up} alt='img' />
+            <span>更多</span>
+          </div>
+          <ul className='btnlist'>
+            {buttonArr.map(item => {
+              return (
+                <li key={item.id} onTouchEnd={(event) => { handleRightBtn(event, item) }}>
+                  <div className='img'>
+                    <img src={item.img} alt="buttonArr" />
+                  </div>
+                  <span>{item.name}</span>
+                </li>
+              )
+            })}
+          </ul>
+        </>
+      )}
+
+
+      {showJianShang && <div className='jianshanglist'>
+        <ul>
+          {jianshangArr.map(item => {
+            return (
+              <li style={{ background: `${item.color}` }} key={item.id} onTouchEnd={() => { setJianshangColor(item) }}>
+              </li>
+            )
+          })}
+        </ul>
+      </div>}
+
+      {/* <div className='btnlist'>
         <ul>
           {cpArr.map(item => {
             return (
@@ -720,16 +1108,35 @@ function App() {
             )
           })}
         </ul>
-      </div>
+      </div> */}
 
-      <div className="reset">
-        <img onTouchEnd={(event) => { handleBtnReset(event) }} src={isReset ? ico_change_active : ico_change_normal} alt='reset' className='reset' />
-      </div>
+      {showJuanZhou && <div className='juanzhou'>
+        <Huajuan
+          handleClick={(item: any) => { setCurrentScale(item) }}
+          onChange={(temp: any) => { setchangeLeft(temp) }}
+          imgs={{
+            img_juanzhou, img_texture, line_boat,
+            line_friends,
+            line_mount,
+            lxuliezhen
+          }}
+          left={changeLeft}
+        />
+
+        <img className='j-close' onTouchEnd={closeAll} src={ico_cancel} alt="" />
+      </div>}
 
       {
-        currentMsg && <Toast bg={imgtext} message={currentMsg}></Toast>
+        currentScale.id &&
+        (currentScale.id === 2 ? <Toast bg={imgtext} message={currentScale.txt}></Toast> :
+          <Scale handleClose={() => { setCurrentScale({}) }} close_img={ico_cancel} txt={currentScale.txt} image={currentScale.img === 'xuliezhen1' ? xuliezhen1 : xuliezhen2}></Scale>)
       }
 
+      {(showInfo || showJianShang || showHistory) && <div onTouchEnd={closeAll} className="shoula">
+        <img src={ico_back} alt='img' />
+      </div>}
+
+
       <div id="webgl" ref={webglRef}></div>
     </div>
   );

BIN
src/assets/images/huaxueqingliao.jpg


BIN
src/assets/images/huiqing.jpg


BIN
src/assets/images/ico_back.png


BIN
src/assets/images/ico_cancel.png


BIN
src/assets/images/ico_change_active.png


BIN
src/assets/images/ico_change_normal.png


BIN
src/assets/images/ico_history_active.png


BIN
src/assets/images/ico_history_normal.png


BIN
src/assets/images/ico_more_normal.png


BIN
src/assets/images/ico_painting_active.png


BIN
src/assets/images/ico_painting_normal.png


BIN
src/assets/images/ico_up.png


BIN
src/assets/images/ico_visual_active.png


BIN
src/assets/images/ico_visual_normal.png


BIN
src/assets/images/line.png


+ 59 - 5
src/assets/images/loadImg.js

@@ -15,8 +15,6 @@ import img_shouxinger from './img_shouxinger.png';
 import img_shouxingniu from './img_shouxingniu.png';
 import img_zimukou from './img_zimukou.png';
 
-import ico_change_active from './ico_change_active.png';
-import ico_change_normal from './ico_change_normal.png';
 
 import img_juanzhou from './img_juanzhou.png';
 import img_texture from './img_texture.jpg';
@@ -25,6 +23,39 @@ import line_boat from './line_boat.png';
 import line_friends from './line_friends.png';
 import line_mount from './line_mount.png';
 import imgtext from './text.png';
+
+
+import ico_history_active from './ico_history_active.png';
+import ico_history_normal from './ico_history_normal.png';
+import ico_painting_active from './ico_painting_active.png';
+import ico_painting_normal from './ico_painting_normal.png';
+import ico_visual_active from './ico_visual_active.png';
+import ico_visual_normal from './ico_visual_normal.png';
+
+import ico_change_active from './ico_change_active.png';
+import ico_change_normal from './ico_change_normal.png';
+
+import ico_back from './ico_back.png';
+import ico_cancel from './ico_cancel.png';
+
+
+import huaxueqingliao from './huaxueqingliao.jpg';
+import zhumingqing from './zhumingqing.jpg';
+import zheliao from './zheliao.jpg';
+import sumaliqing from './sumaliqing.jpg';
+import shiziqing from './shiziqing.jpg';
+import pingdengqing from './pingdengqing.jpg';
+import huiqing from './huiqing.jpg';
+
+
+import lxuliezhen from './xuliezhen.jpg';
+
+import xuliezhen1 from './xuliezhen1.png';
+import xuliezhen2 from './xuliezhen2.png';
+
+
+
+
 export {
   loading_bg,
   img_culture,
@@ -37,12 +68,35 @@ export {
   img_shouxinger,
   img_shouxingniu,
   img_zimukou,
-  ico_change_active,
-  ico_change_normal,
+
   img_juanzhou,
   img_texture,
   line_boat,
   line_friends,
   line_mount,
-  imgtext
+  imgtext,
+
+  ico_change_active,
+  ico_change_normal,
+  ico_history_active,
+  ico_history_normal,
+  ico_painting_active,
+  ico_painting_normal,
+  ico_visual_active,
+  ico_visual_normal,
+
+  ico_back,
+  ico_cancel,
+
+  huaxueqingliao,
+  zhumingqing,
+  zheliao,
+  sumaliqing,
+  shiziqing,
+  huiqing,
+  pingdengqing,
+  lxuliezhen,
+  xuliezhen1,
+  xuliezhen2
+
 }

BIN
src/assets/images/pingdengqing.jpg


BIN
src/assets/images/shiziqing.jpg


BIN
src/assets/images/sumaliqing.jpg


BIN
src/assets/images/xuliezhen.jpg


BIN
src/assets/images/xuliezhen.png


BIN
src/assets/images/xuliezhen1.png


BIN
src/assets/images/xuliezhen2.png


BIN
src/assets/images/xuliezhen222.png


BIN
src/assets/images/zheliao.jpg


BIN
src/assets/images/zhumingqing.jpg


+ 76 - 0
src/components/LongImageSequence/index copy.tsx

@@ -0,0 +1,76 @@
+import React, { useState, useEffect, useRef } from 'react';
+
+interface ImageSequenceProps {
+  image: string; // 长图序列帧的图片路径
+  frameWidth: number; // 单帧图片原始宽度
+  interval: number; // 播放间隔时间(毫秒)
+}
+
+function ImageSequence({ image, frameWidth, interval }: ImageSequenceProps) {
+  const [currentFrame, setCurrentFrame] = useState(0);
+  const containerRef = useRef<HTMLDivElement>(null);
+  const imageRef = useRef<HTMLImageElement>(null);
+  const [imageWidth, setImageWidth] = useState(0);
+
+  useEffect(() => {
+    const containerHeight = containerRef.current?.clientHeight || 0;
+    const frameRatio = frameWidth / containerHeight;
+    const frameWidthScaled = containerHeight * frameRatio;
+    totalFrames = Math.floor(imageWidth / frameWidthScaled) || 0;
+    const intervalId = setInterval(() => {
+      setCurrentFrame((currentFrame) => (currentFrame + 1) % totalFrames);
+    }, interval);
+
+    return () => clearInterval(intervalId);
+  }, [frameWidth, interval, imageWidth]);
+
+  let containerHeight = containerRef.current?.clientHeight || 0;
+  let frameRatio = frameWidth / containerHeight;
+  let frameWidthScaled = containerHeight * frameRatio;
+  let backgroundPositionX = -currentFrame * frameWidthScaled;
+  let totalFrames = 0
+  useEffect(() => {
+    const imageEl = new Image();
+    imageEl.src = image;
+    imageEl.onload = () => {
+      setImageWidth(imageEl.width);
+      setCurrentFrame(0);
+    };
+  }, [image, frameWidth]);
+
+  return (
+    <div
+      ref={containerRef}
+      style={{
+        position: 'relative',
+        width: '100%',
+        height: '100%',
+      }}
+    >
+      <img
+      alt=''
+        ref={imageRef}
+        src={image}
+        style={{ display: 'none' }}
+        onLoad={() => {
+           containerHeight = containerRef.current?.clientHeight || 0;
+           frameRatio = frameWidth / containerHeight;
+           frameWidthScaled = containerHeight * frameRatio;
+           totalFrames = Math.floor(imageWidth / frameWidthScaled) || 0;
+          setCurrentFrame(0);
+        }}
+      />
+      <div
+        style={{
+          width: `${frameWidthScaled}px`,
+          height: '100%',
+          backgroundImage: `url(${image})`,
+          backgroundPosition: `${backgroundPositionX}px 0`,
+          backgroundRepeat: 'no-repeat',
+        }}
+      />
+    </div>
+  );
+}
+
+export default ImageSequence;

+ 40 - 0
src/components/LongImageSequence/index.module.scss

@@ -0,0 +1,40 @@
+.Layout {
+  position: fixed;
+  bottom: 0;
+  top: 0;
+  left: 0;
+  right: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 9999;
+  backdrop-filter: blur(55px);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  :global {
+    .img-con{
+      width: 100%;
+    }
+    .txt{
+      position: absolute;
+      bottom: 25%;
+      width: 90%;
+      display: inline-block;
+      font-size: 18px;
+      text-align: center;
+      color: #fff;
+      line-height: 1.5;
+      left: 50%;
+      opacity: 0;
+      transform: translateX(-50%);
+      animation: fadeIn 3s 0.3s forwards;
+
+    }
+    .j-close {
+      position: absolute;
+      bottom: 12%;
+      left: 50%;
+      transform: translateX(-50%);
+    }
+  }
+}

+ 126 - 0
src/components/LongImageSequence/index.tsx

@@ -0,0 +1,126 @@
+
+interface ImageSequenceProps {
+  image: string; // 长图序列帧的图片路径,
+  handleClick: (item: any) => void
+}
+
+function ImageSequence({ image, handleClick }: ImageSequenceProps) {
+
+  const clickArr = [{
+    id: 1,
+    name: '船',
+    txt: '何时扁潮,处时舟落,青引共江,山领济平,是望与未,越天君有,中末同风',
+    img: 'xuliezhen1',
+    top: '45%',
+    left: '10%',
+    width: '6%',
+    height: '14%',
+  }, {
+    id: 11,
+    name: '船',
+    txt: '何时扁潮,处时舟落,青引共江,山领济平,是望与未,越天君有,中末同风',
+    img: 'xuliezhen1',
+    top: '52%',
+    left: '42%',
+    width: '11%',
+    height: '18%',
+  }, {
+    id: 2,
+    name: '塔',
+    img: 'xuliezhen2',
+    txt: '佛塔多为七层,是因为在佛教中,七层的佛塔是最高等级的佛塔,七级浮屠指的就是七层塔',
+    top: '26%',
+    left: '55%',
+    width: '8%',
+    height: '30%'
+  }, {
+    id: 3,
+    name: '船',
+    img: 'xuliezhen2',
+    txt: '茜愁蝶倦晚芳时,纵是明春再见隔年期',
+    top: '80%',
+    left: '92%',
+    width: '8%',
+    height: '15%'
+  }]
+
+  let xDown: any = null;
+  let yDown: any = null;
+
+  const handleTouchStart = (event: any) => {
+    const firstTouch = event.touches[0];
+    xDown = firstTouch.clientX;
+    yDown = firstTouch.clientY;
+  }
+
+  const ClickOrTouch = (event: any, item: any) => {
+    event.preventDefault()
+
+    if (!xDown || !yDown) {
+      // 用户没有滑动,不触发事件
+      return;
+    }
+
+    const xUp = event.changedTouches[0].clientX;
+    const yUp = event.changedTouches[0].clientY;
+    const xDiff = xUp - xDown;
+    const yDiff = yUp - yDown;
+
+    if (Math.abs(xDiff) > Math.abs(yDiff)) {
+      // 用户左右滑动,触发左右滑动事件
+      return
+    } else if (Math.abs(xDiff) < Math.abs(yDiff)) {
+      // 用户上下滑动,不触发事件
+      return
+
+    }
+
+    xDown = null
+    yDown = null
+    handleClick(item)
+  }
+
+  return (
+    <div style={{
+      height: `100%`,
+      position: 'relative'
+    }}>
+      <img style={{
+        height: `100%`,
+      }} src={image} alt="" />
+
+      {/* <div
+        className='img-con'
+        style={{
+          width: `${1044 * 0.53}px`,
+          height: `${486 * 0.53}px`,
+          backgroundImage: `url(${image})`,
+          backgroundSize: `auto 100%`,
+          // animation: `play_full 3s steps(24) infinite`
+        }}
+      /> */}
+      <ul>
+        {
+          clickArr.map(item => {
+            return (
+              <li key={item.id} style={{
+                position: `absolute`,
+                top: `${item.top}`,
+                left: `${item.left}`,
+                width: `${item.width}`,
+                height: `${item.height}`,
+                backgroundColor: `rgba(0,0,0,0.0)`,
+              }}
+                onTouchStart={handleTouchStart}
+                onTouchEnd={(event: any) => { ClickOrTouch(event, item) }}
+              >
+              </li>
+            )
+          })
+        }
+      </ul>
+    </div>
+  );
+}
+
+export default ImageSequence;

+ 31 - 0
src/components/LongImageSequence/scale.tsx

@@ -0,0 +1,31 @@
+import styles from './index.module.scss';
+
+
+interface ImageSequenceProps {
+  image: string; // 长图序列帧的图片路径,
+  close_img: string,
+  txt: string,
+  handleClose:()=>void
+}
+
+function ImageSequence({ image, close_img,txt,handleClose }: ImageSequenceProps) {
+
+  return (
+    <div className={styles.Layout}>
+      <div
+        className='img-con'
+        style={{
+          width: `${750 * 0.5}px`,
+          height: `${480 * 0.5}px`,
+          backgroundImage: `url(${image})`,
+          backgroundSize: `auto 100%`,
+          animation: `play_son 3s steps(24) infinite`
+        }}
+      />
+      <div className='txt'>{txt}</div>
+      <img className='j-close' onTouchEnd={handleClose} src={close_img} alt="" />
+    </div>
+  );
+}
+
+export default ImageSequence;

+ 12 - 0
src/components/Slider.css

@@ -0,0 +1,12 @@
+.slider-container {
+  overflow: hidden;
+  width: 100%;
+  height: 100%;
+}
+
+.slider-con {
+  width: auto;
+  height: 100%;
+  transition: transform 0.3s ease-out;
+  display: inline-block;
+}

+ 114 - 0
src/components/Slider.tsx

@@ -0,0 +1,114 @@
+import React, { useRef, useState } from 'react';
+import './Slider.css';
+
+interface SliderProps {
+  children: React.ReactNode;
+}
+
+function Slider({ children }: SliderProps) {
+  const containerRef = useRef<HTMLDivElement>(null);
+  const imageRef = useRef<HTMLDivElement>(null);
+  const [isDragging, setIsDragging] = useState(false);
+  const [startPosition, setStartPosition] = useState(0);
+  const [currentTranslate, setCurrentTranslate] = useState(0);
+  const [prevTranslate, setPrevTranslate] = useState(0);
+  const [animationId, setAnimationId] = useState<number | null>(null);
+  const [minTranslate, setMinTranslate] = useState(0);
+  const [maxTranslate, setMaxTranslate] = useState(0);
+
+  const startDragging = (event: any) => {
+    if (event.type === 'touchstart') {
+      setStartPosition(event.touches[0].clientX);
+    } else {
+      setStartPosition(event.clientX);
+    }
+
+    setIsDragging(true);
+    cancelAnimationFrame(animationId!);
+  };
+
+  const dragging = (event: any) => {
+    if (isDragging) {
+      let currentPosition = 0;
+
+      if (event.type === 'touchmove') {
+        currentPosition = event.touches[0].clientX;
+      } else {
+        currentPosition = event.clientX;
+      }
+
+      const diff = currentPosition - startPosition;
+      const newTranslate = prevTranslate + diff;
+      const newMinTranslate = containerRef.current!.offsetWidth - imageRef.current!.offsetWidth;
+      const newMaxTranslate = 0;
+
+      if (newTranslate < newMinTranslate) {
+        setCurrentTranslate(newMinTranslate);
+      } else if (newTranslate > newMaxTranslate) {
+        setCurrentTranslate(newMaxTranslate);
+      } else {
+        setCurrentTranslate(newTranslate);
+      }
+    }
+  };
+
+  const stopDragging = () => {
+    setIsDragging(false);
+
+    if (currentTranslate < minTranslate) {
+      setCurrentTranslate(minTranslate);
+    } else if (currentTranslate > maxTranslate) {
+      setCurrentTranslate(maxTranslate);
+    }
+
+    setPrevTranslate(currentTranslate);
+
+    setAnimationId(requestAnimationFrame(() => {
+      imageRef.current!.style.transition = 'transform 0.3s ease-out';
+      imageRef.current!.style.transform = `translateX(${currentTranslate}px)`;
+    }));
+  };
+
+  const updateBoundaries = () => {
+    if (containerRef.current && imageRef.current) {
+      const containerWidth = containerRef.current.offsetWidth;
+      const imageWidth = imageRef.current.offsetWidth;
+      setMinTranslate(containerWidth - imageWidth);
+      setMaxTranslate(0);
+    }
+  };
+
+  return (
+    <div
+      className="slider-container"
+      ref={containerRef}
+      onMouseDown={startDragging}
+      onTouchStart={startDragging}
+      onMouseUp={stopDragging}
+      onTouchEnd={stopDragging}
+      onMouseLeave={stopDragging}
+      onMouseMove={dragging}
+      onTouchMove={dragging}
+      onWheel={(event) => {
+        event.preventDefault();
+        setCurrentTranslate((prevTranslate) => {
+          const scrollDistance = event.deltaY * -1;
+          const maxScrollDistance = maxTranslate - prevTranslate;
+          const minScrollDistance = minTranslate - prevTranslate;
+          const scrollAmount =
+            scrollDistance > 0
+              ? Math.min(scrollDistance, maxScrollDistance)
+              : Math.max(scrollDistance, minScrollDistance);
+          return prevTranslate + scrollAmount;
+        });
+      }}
+      onResize={updateBoundaries}
+    >
+      <div className="slider-con" ref={imageRef}>
+        {children}
+      </div>
+    </div>
+  );
+}
+
+export default Slider;

+ 2 - 2
src/components/Toast/index.module.scss

@@ -20,9 +20,9 @@
       display: flex;
       align-items: center;
       text-align: center;
-      max-width: 80%;
+      max-width: 95%;
       min-height: 50px;
-      min-width: 70%;
+      min-width: 90%;
       line-height: 1.5;
       transition: opacity 0.3s ease-in-out;
     }

+ 9 - 17
src/components/huajuan.tsx

@@ -1,6 +1,11 @@
 import React, { FunctionComponent, useEffect, useMemo, useRef } from 'react';
 import styles from './index.module.scss';
 
+import Slider from './Slider';
+
+import ImageSequence from './LongImageSequence';
+
+
 
 interface IProps {
   imgs: any,
@@ -59,27 +64,14 @@ const Component: FunctionComponent<IProps> = (props) => {
     onChange(temp)
   }, [left])
 
-
-
   return (
     <div className={styles.Layout}>
       <img ref={jzimg} className='hjbg' src={imgs.img_juanzhou} alt="img_juanzhou" />
       <div className='inner'>
-        <div className='innerimg'
-          ref={innerimg}
-          style={{ left: `-${left}px` }}>
-          <img
-            className='texture'
-            src={imgs.img_texture} alt="img_texture" />
-          {
-            info.map(item => {
-              return (
-                <img key={item.id} onTouchEnd={(event) => {event.preventDefault(); handleClick(item) }} className={`gaoliang ${item.cls}`} src={item.image} alt={item.name} />
-              )
-            })
-          }
-
-        </div>
+        <Slider>
+          {/* <div style={{width:'1024px',height:'100%',background:'#000'}}></div> */}
+          <ImageSequence image={imgs.lxuliezhen} handleClick={(item:any)=>{handleClick(item)}} />
+        </Slider>
       </div>
     </div>
   );

+ 12 - 0
src/components/index.module.scss

@@ -17,6 +17,7 @@
     }
     .inner {
       height: 80%;
+      width: calc(100% - 32px);
       position: relative;
       &::before {
         position: absolute;
@@ -28,6 +29,16 @@
         width: 30px;
         background: linear-gradient(to right, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0));
       }
+      .inner-con{
+        height: 100%;
+        width: 100%;
+        .swiper-slide{
+          height: 100%;
+          .texture{
+            height: 100%;
+          }
+        }
+      }
       .innerimg {
         height: 100%;
         position: relative;
@@ -56,6 +67,7 @@
           top: 50%;
         }
       }
+      
     }
   }
 }

+ 32 - 3
src/index.css

@@ -1,12 +1,12 @@
-@font-face {
+/* @font-face {
   font-family: 'siyuan';
   src: url(./assets/fonts/SourceHanSerifCN-Bold-2.otf);
   font-weight: normal;
   font-style: normal;
-}
+} */
 
 * {
-  font-family: 'siyuan';
+  font-family: "kaiti","楷体","楷体_GB2312"!important;
   margin: 0;
   padding: 0;
   list-style: none;
@@ -35,4 +35,33 @@ body,
   100% {
     opacity: 0.2;
   }
+}
+
+@keyframes play_full {
+  0% {
+    background-position: 0 0;
+  }
+  100% {
+    background-position: calc(-100224px * 0.53) 0;
+  }
+}
+
+
+@keyframes play_son {
+  0% {
+    background-position: 0 0;
+  }
+  100% {
+    background-position: calc(-72000px * 0.5) 0;
+  }
+}
+
+
+@keyframes fadeIn {
+  0% {
+    opacity: 0;
+  }
+  100% {
+    opacity: 1;
+  }
 }

+ 131 - 1
src/utils/index.tsx

@@ -1,10 +1,13 @@
+import * as THREE from "three";
+
+
 export function debounce(func: Function, delay: number): Function {
   let timer: any;
   return function () {
     if (timer) {
       clearTimeout(timer);
     }
-    timer = setTimeout(()=> {
+    timer = setTimeout(() => {
       func();
     }, delay);
   };
@@ -20,4 +23,131 @@ export function throttle(func: Function, delay: number): Function {
       func();
     }
   };
+}
+
+export class TextureAnimatorFn {
+  textures;
+  tilesHorizontal;
+  tilesVertical;
+  numberOfTiles;
+  tileDisplayDuration;
+  currentDisplayTime;
+  currentTile;
+  name;
+
+  constructor(t: any, n: any, r: any, a: any, o: any, name: string) {
+    this.textures = t
+    this.tilesHorizontal = n
+    this.tilesVertical = r
+    this.numberOfTiles = a
+    this.textures.wrapS = this.textures.wrapT = THREE.RepeatWrapping
+    this.textures.repeat.set(1 / this.tilesHorizontal, 1 / this.tilesVertical)
+    this.tileDisplayDuration = o
+    this.currentDisplayTime = 0
+    this.currentTile = 0
+    this.name = name
+  }
+
+  update(e: any) {
+    for (this.currentDisplayTime += e; this.currentDisplayTime > this.tileDisplayDuration;) {
+      this.currentDisplayTime -= this.tileDisplayDuration
+      this.currentTile++
+      this.currentTile == this.numberOfTiles && (this.currentTile = 0);
+      var t = this.currentTile % this.tilesHorizontal;
+      this.textures.offset.x = t / this.tilesHorizontal;
+      var n = Math.floor(this.currentTile / this.tilesHorizontal);
+      this.textures.offset.y = n / this.tilesVertical
+
+    }
+  }
+}
+
+
+export class Hotspot {
+  nameStr: any
+  reticleAnim: any
+  textureAnimator: any
+  nameMesh: any
+  line: any
+  maxParticleCount: any
+  hideTime: any
+  showTime: any
+
+  static scene: THREE.Scene;
+  static camera: THREE.Camera | THREE.PerspectiveCamera | undefined;
+
+
+  initLabel(data: any) {
+    var textrue = (new THREE.TextureLoader).load('model/reticle-animation.png');
+    this.textureAnimator = new TextureAnimatorFn(textrue, 50, 1, 50, 50, data.name);
+    console.log('result:', this.textureAnimator);
+    var plane = new THREE.PlaneGeometry(2, 2, 1, 1),
+      material = new THREE.MeshBasicMaterial({
+        map: textrue,
+        side: 2,
+        transparent: !0,
+        opacity: 1
+      });
+    this.reticleAnim = new THREE.Mesh(plane, material)
+    this.reticleAnim.lookAt(Hotspot.camera!.position)
+    this.reticleAnim.name = data.name
+    this.reticleAnim.userData.depthlevel = 1
+    let scale = .03
+    this.reticleAnim.scale.copy(new THREE.Vector3(scale, scale, scale))
+    this.reticleAnim.position.copy(data.posInModel)
+    Hotspot.scene.add(this.reticleAnim)
+    this.initName(data)
+  }
+
+  initName(data: any) {
+    var planeGeometry = new THREE.PlaneGeometry(228, 111);
+    let textrue: any = data.title;
+    textrue.magFilter = THREE.LinearFilter;
+    textrue.minFilter = THREE.LinearFilter;
+    textrue.encoding = THREE.LinearEncoding;
+    var material = new THREE.MeshBasicMaterial({
+      color: 16777215,
+      map: textrue,
+      transparent: !0,
+      opacity: 1
+    });
+    this.nameMesh = new THREE.Mesh(planeGeometry, material);
+    let scale = .0004
+    this.nameMesh.scale.set(scale, scale, scale)
+    this.nameMesh.position.copy(data.titleInPoint)
+    this.nameMesh.name = data.name
+    this.nameMesh.userData.depthlevel = 1
+    Hotspot.scene.add(this.nameMesh)
+
+    this.setPosition(data)
+  }
+
+  hide(){
+    this.reticleAnim.visible = false
+    this.nameMesh.visible = false
+    this.line.visible = false
+  }
+
+  show(){
+    this.reticleAnim.visible = true
+    this.nameMesh.visible = true
+    this.line.visible = true
+  }
+
+  setPosition(data: any) {
+    var n = new THREE.LineBasicMaterial({
+      color: 6518381,
+      linewidth: 1,
+      opacity: 1
+    });
+    let r = new THREE.Vector3(0, 0, 0).copy(data.posInModel);
+    let a = new THREE.Vector3(data.titleInPoint.x, data.titleInPoint.y - 0.015, data.titleInPoint.z);
+    let i = (new THREE.BufferGeometry).setFromPoints([r, a]);
+    this.line = new THREE.Line(i, n)
+    this.line.name = data.name
+    Hotspot.scene.add(this.line)
+    // reticleAnim.visible = !1
+    // nameMesh.visible = !1
+    // line.visible = !1
+  }
 }

+ 12 - 0
yarn.lock

@@ -8076,6 +8076,11 @@ sprintf-js@~1.0.2:
   resolved "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz"
   integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
 
+ssr-window@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.npmmirror.com/ssr-window/-/ssr-window-4.0.2.tgz#dc6b3ee37be86ac0e3ddc60030f7b3bc9b8553be"
+  integrity sha512-ISv/Ch+ig7SOtw7G2+qkwfVASzazUnvlDTwypdLoPoySv+6MqlOV10VwPSE6EWkGjhW50lUmghPmpYZXMu/+AQ==
+
 stable@^0.1.8:
   version "0.1.8"
   resolved "https://registry.npmmirror.com/stable/-/stable-0.1.8.tgz"
@@ -8346,6 +8351,13 @@ svgo@^2.7.0:
     picocolors "^1.0.0"
     stable "^0.1.8"
 
+swiper@^9.2.4:
+  version "9.2.4"
+  resolved "https://registry.npmmirror.com/swiper/-/swiper-9.2.4.tgz#2fa3cf58cef586366f674a10fa56fe6eec2026fe"
+  integrity sha512-L7y3K/iiMXNYQ94FbfcJn7jex4QPnS4+voXGupTdC+UHW4XrR40QDdm4c9hXJ+Br0Il7PP0vP1W3goM9/Ly6Sg==
+  dependencies:
+    ssr-window "^4.0.2"
+
 symbol-tree@^3.2.4:
   version "3.2.4"
   resolved "https://registry.npmmirror.com/symbol-tree/-/symbol-tree-3.2.4.tgz"