소스 검색

Merge branch 'feature-tianyong-newVersion' into ktyq-test-new

TIANYONG 8 달 전
부모
커밋
62c3fc2ab3

+ 2 - 1
src/page-instrument/header-top/index.module.less

@@ -132,7 +132,8 @@
     display: flex;
     align-items: center;
     height: 100%;
-
+    position: relative;
+    z-index: 9;
     .btn {
         position: relative;
         display: flex;

+ 6 - 2
src/page-instrument/header-top/index.tsx

@@ -1,4 +1,4 @@
-import { Transition, computed, defineComponent, onMounted, onUnmounted, reactive, ref, watch, toRef, ComputedRef, nextTick } from "vue";
+import { Transition, computed, defineComponent, onMounted, onUnmounted, reactive, ref, watch, toRef, ComputedRef, nextTick, defineAsyncComponent } from "vue";
 import styles from "./index.module.less";
 
 import iconBack from "./image/icon-back.png";
@@ -28,12 +28,16 @@ import { toggleMusicSheet } from "/src/view/plugins/toggleMusicSheet";
 import useDrag from "/src/view/plugins/useDrag/index";
 import Dragbom from "/src/view/plugins/useDrag/dragbom";
 import { getGuidance, setGuidance } from "../custom-plugins/guide-page/api";
-import ModeView from "./modeView";
+// import ModeView from "./modeView";
 import { smoothAnimationState } from "../view-detail/smoothAnimation";
 import { isMusicList, musicListShow } from "../component/the-music-list";
 import { EvaluatingDriver, FollowDriver, PractiseDriver } from "../custom-plugins/guide-driver";
 import { fingerRef } from "/src/page-instrument/view-detail/index"
 
+const ModeView = defineAsyncComponent(() =>
+  import('./modeView')
+)
+
 /** 头部数据和方法 */
 export const headTopData = reactive({
   /** 模式 */

+ 28 - 6
src/page-instrument/view-detail/index.tsx

@@ -37,8 +37,8 @@ import AuthorName from "../component/authorName";
 import { initSmoothAnimation } from "./smoothAnimation";
 import EmptyMusic, { isEmptyMusicShow } from "./emptyMusic";
 import { position } from "html2canvas/dist/types/css/property-descriptors/position";
-import Loading from "./loading";
-import bgJson from "./images/index.json";
+import Loading from "./loading"
+// import bgJson from "./images/index.json";
 import bg2Left from "./images/bg2_left_zs.png";
 import bg2Right from "./images/bg2_right_zs.png";
 
@@ -117,7 +117,7 @@ export default defineComponent({
       }
     };
     onBeforeMount(async () => {
-      console.time("渲染加载耗时");
+      // console.time("渲染加载耗时");
       api_keepScreenLongLight();
       getAPPData();
       api_setStatusBarVisibility();
@@ -144,6 +144,20 @@ export default defineComponent({
     };
     // console.log(route.params, query)
 
+    // 动态加载json背景
+    const bgJsonData: any = ref(null);
+    const jsonFiles: any = {
+      data: () => import('./images/index.json'),
+    };
+    const loadJson = async (fileKey: any) => {
+      try {
+         const data = await jsonFiles[fileKey]();
+        bgJsonData.value = data.default;
+      } catch (error) {
+        console.error('👀~json背景加载失败', error);
+      }
+    };
+
     onMounted(async () => {
       (window as any).appName = "colexiu";
       const id = query.id || "43554";
@@ -160,7 +174,8 @@ export default defineComponent({
       // Promise.all([sysMusicScoreAccompanimentQueryPage(id)]).then((values) => {
       //   getMusicInfo(values[0]);
       // });
-      try {
+      loadJson('data');
+      try { 
         await getMusicDetail(id);
       } catch (err) {
         console.error(err);
@@ -476,8 +491,15 @@ export default defineComponent({
           background: state.setting.camera ? `rgba(${state.setting.eyeProtection ? "253,244,229" : "255,255,255"} ,${state.setting.cameraOpacity / 100}) !important` : "",
         }}
       >
-        <img style={{ opacity: state.setting.camera && state.modeType === "evaluating" ? state.setting.cameraOpacity / 100 : 1 }} class={styles.pageBg} src={state.modeType === "practise" ? bgJson[1] : state.modeType === "evaluating" ? bgJson[2] : state.modeType === "follow" ? bgJson[3] : ""} />
-        {state.modeType === "evaluating" ? (
+        {bgJsonData.value ? 
+          <img 
+            style={{opacity: state.setting.camera && state.modeType === 'evaluating' ? state.setting.cameraOpacity / 100 : 1}} 
+            class={styles.pageBg} 
+            src={state.modeType === 'practise' ? bgJsonData.value[1] : state.modeType === 'evaluating' ? bgJsonData.value[2] : state.modeType === 'follow' ? bgJsonData.value[3] : ''} 
+          /> : null    
+        }
+        {
+          state.modeType === 'evaluating' ? (
           <>
             <img src={bg2Left} class={styles.bg2Left} />
             <img src={bg2Right} class={styles.bg2Right} />

+ 99 - 176
src/page-instrument/view-detail/smoothAnimation/index.ts

@@ -14,6 +14,7 @@ type smoothAnimationType = {
    canvasCtx: null | undefined | CanvasRenderingContext2D
    canvasDomWith: number
    canvasDomHeight: number
+   canvasSmoothDom: null | HTMLCanvasElement
    smoothAnimationBoxDom: null | HTMLElement
    smoothBotDom: null | HTMLElement
    osmdCanvasPageDom: null | HTMLElement
@@ -21,13 +22,14 @@ type smoothAnimationType = {
    osdmScrollDomWith: number
    osdmScrollDomOffsetLeft: number
    selectionBoxDom: null | HTMLElement
+   batePos: pointsPosType
    pointsPos: pointsPosType
    translateXNum: number
    aveSpeed: number
 }
 
-const _numberOfSegments = 58 // 中间切割线的个数
-const _canvasDomHeight = 60
+let _numberOfSegments = 56 // 中间切割线的个数
+const _canvasDomHeight = 60 // canvans 高度
 
 export const smoothAnimationState = {
    isShow: ref(false), // 是否显示
@@ -35,6 +37,7 @@ export const smoothAnimationState = {
    canvasCtx: null,
    canvasDomWith: 0,
    canvasDomHeight: _canvasDomHeight,
+   canvasSmoothDom: null,
    smoothAnimationBoxDom: null,
    smoothBotDom: null,
    osmdCanvasPageDom: null,
@@ -42,7 +45,8 @@ export const smoothAnimationState = {
    osdmScrollDomWith: 0,
    osdmScrollDomOffsetLeft: 0,
    selectionBoxDom: null,
-   pointsPos: [], // 计算之后的点坐标数组
+   batePos: [], // times 直接转换的数组
+   pointsPos: [], // 筛选之后的点坐标数组
    translateXNum: 0, // 当前谱面的translateX的距离   谱面的位置信息 由translateX和scrollLeft的偏移一起决定
    aveSpeed: 0 // 谱面的一帧的平均速度
 } as smoothAnimationType
@@ -64,11 +68,16 @@ export function initSmoothAnimation() {
    createSmoothAnimation()
    // 初始化动画数据
    const batePos = getPointsPosByBatePos()
-   console.log(batePos, "batePos")
+   smoothAnimationState.batePos = batePos
    const batePos1 = dataFilter([...batePos])
-   const batePos2 = createSmoothCurvePoints(batePos1, undefined, undefined, _numberOfSegments)
-   const batePos3 = dataFilter2(batePos, batePos2)
-   smoothAnimationState.pointsPos = batePos3
+   console.log(batePos1, "排序之后的数据")
+   // 这里性能优化,对于超级长的曲子,_numberOfSegments值 动态变化
+   const numberOfSegments = parseInt(16000 / batePos1.length + "")
+   _numberOfSegments = Math.max(18, Math.min(_numberOfSegments, numberOfSegments))
+   const batePos2 = createSmoothCurvePoints(batePos1, _numberOfSegments)
+   smoothAnimationState.pointsPos = batePos2
+   // 初始化旋律线
+   initCanvasSmooth()
    // 谱面的平均速度(因为可能有反复的情况所以实际距离要加上反复的距离)
    const canvasDomPath = batePos.reduce((path, item, index, arr) => {
       if (index !== 0) {
@@ -94,25 +103,28 @@ export function initSmoothAnimation() {
 
 // 排序
 function dataFilter(batePos: pointsPosType) {
+   // 去掉反复跳房子的数据
    const filterData = batePos.filter((item, index, array) => {
       return array.findIndex(i => i.noteId === item.noteId) === index
    })
-   // 先按 音符排序  因为音符可能有重复id
+   // 先按 音符排序
    const sortedData = filterData.sort((a, b) => a.noteId - b.noteId)
    // 再按 小节排序
    return sortedData.sort((a, b) => a.MeasureNumberXML - b.MeasureNumberXML)
 }
 
-//给原始数据赋值
-function dataFilter2(batePos1: pointsPosType, batePos2: pointsPosType) {
-   return batePos1.reduce((arr: pointsPosType, { noteId, MeasureNumberXML }) => {
-      const noteIdIndex = batePos2.findIndex(item => {
-         return noteId === item.noteId && MeasureNumberXML === item.MeasureNumberXML
-      })
-      arr.push(...batePos2.slice(noteIdIndex, noteIdIndex + _numberOfSegments + 1))
-      return arr
-   }, [])
+//根据 activeInde和进度 查找转换之后的activeInde
+function dataFindIndex(activeIndex: number, progress: number) {
+   // 百分比转为当前的index 距离个数
+   const progressCalcIndex = Math.round(progress * _numberOfSegments)
+   const { noteId, MeasureNumberXML } = smoothAnimationState.batePos[activeIndex]
+   return (
+      smoothAnimationState.pointsPos.findIndex(item => {
+         return item.noteId === noteId && item.MeasureNumberXML === MeasureNumberXML
+      }) + progressCalcIndex
+   )
 }
+
 /**
  * 销毁
  */
@@ -125,6 +137,7 @@ export function destroySmoothAnimation() {
       canvasCtx: null,
       canvasDomWith: 0,
       canvasDomHeight: _canvasDomHeight,
+      canvasSmoothDom: null,
       smoothAnimationBoxDom: null,
       smoothBotDom: null,
       osmdCanvasPageDom: null,
@@ -132,6 +145,7 @@ export function destroySmoothAnimation() {
       osdmScrollDomWith: 0,
       osdmScrollDomOffsetLeft: 0,
       selectionBoxDom: null,
+      batePos: [],
       pointsPos: [],
       translateXNum: 0,
       aveSpeed: 0
@@ -150,12 +164,7 @@ export function moveSmoothAnimationByPlayTime(time?: number) {
    if (currentTime <= state.fixtime) return
    if (currentTime > state.times.last()?.endtime) return
    // 当休止小节,可能当前音符在谱面上没有实际的音符(没有bbox),所以往后找谱面上有的音符
-   let nextIndex = state.activeNoteIndex + 1
-   let nextBBox = state.times[nextIndex]?.bbox
-   while (!nextBBox && nextIndex < state.times.length) {
-      nextIndex += 1
-      nextBBox = state.times[nextIndex]?.bbox
-   }
+   const nextIndex = state.activeNoteIndex + 1
    // 当前的音符和下一个音符之间的时值 (当是最后一个音符的时候,下一个音符的时间取当前音符的endtime)
    const noteDuration =
       (nextIndex > state.times.length - 1 ? state.times[state.activeNoteIndex]?.endtime : state.times[nextIndex].time) -
@@ -180,26 +189,19 @@ export function moveSmoothAnimation(progress: number, activeIndex: number, isMov
    // if (!smoothAnimationState.isShow.value) {
    //    return
    // }
-   // 计算 下一个音符index 在pointsPos 中的距离
-   const nextPointsIndex = (activeIndex + 1) * (_numberOfSegments + 1) - 1
-   // 百分比转为当前的index 距离个数
-   const progressCalcIndex = Math.round(progress * _numberOfSegments)
-   // // 当前的index
-   const nowIndex = nextPointsIndex - _numberOfSegments + progressCalcIndex
+   const nowIndex = dataFindIndex(activeIndex, progress)
    const nowPointsPos = smoothAnimationState.pointsPos[nowIndex]
    // 当x的值为null和undefinedde的时候 错误 不走下面的方法
    if (!(nowPointsPos?.x != null)) {
       console.error(nowPointsPos?.x, "nowPointsPos", nowIndex, activeIndex)
       return
    }
-   smoothAnimationState.canvasCtx?.clearRect(0, 0, smoothAnimationState.canvasDomWith, smoothAnimationState.canvasDomHeight)
    // 移动
    smoothAnimationMove(
       {
-         x: nowPointsPos.x - 18,
+         x: nowPointsPos.x - 18, //鸟的大小
          y: nowPointsPos.y - 23
       },
-      smoothAnimationState.pointsPos,
       smoothAnimationState.pointsPos.slice(0, nowIndex)
    )
    // 当移动到屏幕最右边时候 就不进行移动了    存在移动到屏幕最右边时候  有反复的情况需要屏幕移动。所以这里注释掉了
@@ -288,17 +290,11 @@ export function moveTranslateXNum(translateXNum: number) {
 /**
  * 进度条和块移动方法
  */
-function smoothAnimationMove(pos: { x: number; y: number }, pointsPos: pointsPosType, progresspointsPos?: pointsPosType) {
+function smoothAnimationMove(pos: { x: number; y: number }, progresspointsPos: pointsPosType) {
    smoothAnimationState.smoothBotDom && (smoothAnimationState.smoothBotDom.style.transform = `translate(${pos.x}px, ${pos.y}px)`)
-   smoothAnimationState.canvasCtx && drawSmoothCurve(smoothAnimationState.canvasCtx, pointsPos, progresspointsPos)
-}
-/**
- * 计算视口宽度
- */
-export function calcClientWidth() {
-   smoothAnimationState.osdmScrollDomWith = smoothAnimationState.osdmScrollDom?.offsetWidth || 0
-   smoothAnimationState.osdmScrollDomOffsetLeft = smoothAnimationState.osdmScrollDom?.getBoundingClientRect().left || 0
+   smoothAnimationState.canvasCtx && drawSmoothCurveProgress(smoothAnimationState.canvasCtx, progresspointsPos, "#FFC121")
 }
+
 /**
  * 创建dom
  */
@@ -328,7 +324,11 @@ function createSmoothAnimation() {
    smoothAnimationState.canvasDomWith = osmdCanvasPageDom?.offsetWidth || 0
    smoothCanvasDom.width = smoothAnimationState.canvasDomWith
    smoothCanvasDom.height = smoothAnimationState.canvasDomHeight
-   smoothAnimationState.canvasCtx = smoothCanvasDom.getContext("2d")
+   const ctx = smoothCanvasDom.getContext("2d")!
+   smoothAnimationState.canvasCtx = ctx
+   ctx.imageSmoothingEnabled = true
+   ctx.lineCap = "round"
+   ctx.lineJoin = "round"
    // bot
    const smoothBotDom = document.createElement("div")
    smoothBotDom.className = "smoothBot"
@@ -343,6 +343,14 @@ function createSmoothAnimation() {
 }
 
 /**
+ * 计算视口宽度
+ */
+export function calcClientWidth() {
+   smoothAnimationState.osdmScrollDomWith = smoothAnimationState.osdmScrollDom?.offsetWidth || 0
+   smoothAnimationState.osdmScrollDomOffsetLeft = smoothAnimationState.osdmScrollDom?.getBoundingClientRect().left || 0
+}
+
+/**
  * 根据音符获取坐标
  */
 function getPointsPosByBatePos(): pointsPosType {
@@ -351,7 +359,7 @@ function getPointsPosByBatePos(): pointsPosType {
       return !item.frequency || item.frequency === -1 ? 0 : item.frequency
    })
    // 线性频率数据
-   const frequencyLineData = quantileScale(frequencyData, 8, _canvasDomHeight - 8)
+   const frequencyLineData = quantileScale(frequencyData, 8, _canvasDomHeight - 8) // 最小值和最大值
    const pointsPos = state.times.reduce((posArr: any[], item, index) => {
       // 当休止小节,可能当前音符在谱面上没有实际的音符(没有bbox)
       if (item.bbox?.x != null && item.noteId != null) {
@@ -398,150 +406,65 @@ function quantileScale(data: number[], minRange = 0, maxRange = _canvasDomHeight
 /**
  * 使用传入的曲线的顶点坐标创建平滑曲线的顶点。
  * @param  {Array}   points  曲线顶点坐标数组,
- * @param  {Float}   tension 密集程度,默认为 0.5
- * @param  {Boolean} closed  是否创建闭合曲线,默认为 false
- * @param  {Int}     numberOfSegments 平滑曲线 2 个顶点间的线段数,默认为 20
+ * @param  {Int}     numberOfSegments 平滑曲线 2 个顶点间的线段数
  * @return {Array}   平滑曲线的顶点坐标数组
  */
-function createSmoothCurvePoints(pointsPos: pointsPosType, tension?: number, closed?: boolean, numberOfSegments?: number) {
-   if (pointsPos.length <= 2) {
-      return pointsPos
+function createSmoothCurvePoints(points: pointsPosType, numSegments: number) {
+   if (points.length <= 2) {
+      return points
    }
-   tension = tension ? tension : 0.5
-   closed = closed ? true : false
-   numberOfSegments = numberOfSegments ? numberOfSegments : 20
-   let ps = pointsPos.slice(0),
-      result = [],
-      x,
-      y,
-      t1x,
-      t2x,
-      t1y,
-      t2y,
-      c1,
-      c2,
-      c3,
-      c4,
-      st,
-      t,
-      i
-   if (closed) {
-      ps.unshift(pointsPos[pointsPos.length - 1])
-      ps.unshift(pointsPos[pointsPos.length - 1])
-      ps.push(pointsPos[0])
-   } else {
-      ps.unshift(pointsPos[0])
-      ps.push(pointsPos[pointsPos.length - 1])
-   }
-   for (i = 1; i < ps.length - 2; i += 1) {
-      //console.log(ps[i + 1].MeasureNumberXML, ps[i - 1].MeasureNumberXML, ps[i + 2].MeasureNumberXML, ps[i].MeasureNumberXML)
-      t1x = (ps[i + 1].x - ps[i - 1].x) * tension
-      t2x = (ps[i + 2].x - ps[i].x) * tension
-      t1y = (ps[i + 1].y - ps[i - 1].y) * tension
-      t2y = (ps[i + 2].y - ps[i].y) * tension
-      // // 当中途出现反复 刚开始反复时候 53 52 22 52  (22)中途值会变小 这里强行拉大 防止算法平均值出现很大偏差
-      // if (ps[i + 1].MeasureNumberXML - ps[i + 2].MeasureNumberXML > 1) {
-      //    const nowNumberXML = ps[i + 1].MeasureNumberXML + 1
-      //    //在当前值的情况下 向前一位
-      //    let index = ps.findIndex(item => {
-      //       return nowNumberXML === item.MeasureNumberXML
-      //    })
-      //    // 查询不到index时候取当前值
-      //    index === -1 && (index = i + 1)
-      //    t2x = (ps[index].x - ps[i].x) * tension
-      // }
-      // // 当中途出现反复 结束反复时候 22 53 22 22  (53)中途值会变大 这里强行缩小 防止算法平均值出现很大偏差
-      // if (ps[i - 1].MeasureNumberXML - ps[i].MeasureNumberXML > 1) {
-      //    //在当前值的情况下 向后一位
-      //    const nowNumberXML = ps[i].MeasureNumberXML - 1
-      //    let index = ps.findIndex((item, index) => {
-      //       return nowNumberXML === item.MeasureNumberXML && nowNumberXML !== ps[index + 1]?.MeasureNumberXML
-      //    })
-      //    // 查询不到index时候取当前值
-      //    index === -1 && (index = i)
-      //    t1x = (ps[i + 1].x - ps[index].x) * tension
-      // }
-      // // 当中途出现跳房子 刚开始跳房子时候 35 35 54 35  (54)中途值会变大 这里强行缩小 防止算法平均值出现很大偏差
-      // if (ps[i + 1].MeasureNumberXML - ps[i + 2].MeasureNumberXML < -1) {
-      //    const nowNumberXML = ps[i + 1].MeasureNumberXML + 1
-      //    //在当前值的情况下 向前一位
-      //    let index = ps.findIndex(item => {
-      //       return nowNumberXML === item.MeasureNumberXML
-      //    })
-      //    // 查询不到index时候取当前值
-      //    index === -1 && (index = i + 1)
-      //    t2x = (ps[index].x - ps[i].x) * tension
-      // }
-      // // 当中途出现跳房子 结束跳房子时候 54 35 54 54  (35)中途值会变小 这里强行拉大 防止算法平均值出现很大偏差
-      // if (ps[i - 1].MeasureNumberXML - ps[i].MeasureNumberXML < -1) {
-      //    const nowNumberXML = ps[i].MeasureNumberXML - 1
-      //    let index = ps.findIndex((item, index) => {
-      //       return nowNumberXML === item.MeasureNumberXML && nowNumberXML !== ps[index + 1]?.MeasureNumberXML
-      //    })
-      //    // 查询不到index时候取当前值
-      //    index === -1 && (index = i)
-      //    t1x = (ps[i + 1].x - ps[index].x) * tension
-      // }
-      // const nowMeasureNumberXML = pointsPos[i - 1].MeasureNumberXML
-      // const nextMeasureNumberXML = pointsPos[i].MeasureNumberXML
-      for (t = 0; t <= numberOfSegments; t++) {
-         // // 小于1时候是反复   大于1是跳房子  不画曲线  停留
-         // if (nextMeasureNumberXML - nowMeasureNumberXML < 0 || nextMeasureNumberXML - nowMeasureNumberXML > 1) {
-         //    //console.log(x, y)
-         //    result.push({
-         //       x: x as number,
-         //       y: y as number,
-         //       MeasureNumberXML: nowMeasureNumberXML,
-         //       noteId: pointsPos[i - 1].noteId
-         //    })
-         //    continue
-         // }
-         st = t / numberOfSegments
-         c1 = 2 * Math.pow(st, 3) - 3 * Math.pow(st, 2) + 1
-         c2 = -(2 * Math.pow(st, 3)) + 3 * Math.pow(st, 2)
-         c3 = Math.pow(st, 3) - 2 * Math.pow(st, 2) + st
-         c4 = Math.pow(st, 3) - Math.pow(st, 2)
-         x = c1 * ps[i].x + c2 * ps[i + 1].x + c3 * t1x + c4 * t2x
-         y = c1 * ps[i].y + c2 * ps[i + 1].y + c3 * t1y + c4 * t2y
-         //console.log(x, y)
-         result.push({
-            x,
-            y,
-            // MeasureNumberXML: t === numberOfSegments ? nextMeasureNumberXML : nowMeasureNumberXML,
-            // noteId: t === numberOfSegments ? pointsPos[i].noteId : pointsPos[i - 1].noteId
-            MeasureNumberXML: pointsPos[i - 1].MeasureNumberXML,
-            noteId: pointsPos[i - 1].noteId
-         })
+   const curvePoints = []
+   for (let i = 0; i < points.length - 1; i++) {
+      const p0 = i > 0 ? points[i - 1] : points[i]
+      const p1 = points[i]
+      const p2 = points[i + 1]
+      const p3 = i !== points.length - 2 ? points[i + 2] : points[i + 1]
+      for (let j = 0; j < numSegments; j++) {
+         const t = j / numSegments
+         const t2 = t * t
+         const t3 = t2 * t
+         const x = 0.5 * (2 * p1.x + (-p0.x + p2.x) * t + (2 * p0.x - 5 * p1.x + 4 * p2.x - p3.x) * t2 + (-p0.x + 3 * p1.x - 3 * p2.x + p3.x) * t3)
+         const y = 0.5 * (2 * p1.y + (-p0.y + p2.y) * t + (2 * p0.y - 5 * p1.y + 4 * p2.y - p3.y) * t2 + (-p0.y + 3 * p1.y - 3 * p2.y + p3.y) * t3)
+         curvePoints.push({ x, y, MeasureNumberXML: p1.MeasureNumberXML, noteId: p1.noteId })
       }
    }
-   return result
+   return curvePoints
+}
+
+/** 初始化一条完整的旋律线dom */
+function initCanvasSmooth() {
+   const smoothDom = document.createElement("canvas")
+   smoothDom.width = smoothAnimationState.canvasDomWith
+   smoothDom.height = smoothAnimationState.canvasDomHeight
+   const smoothDomCtx = smoothDom.getContext("2d")!
+   smoothDomCtx.imageSmoothingEnabled = true
+   smoothDomCtx.lineCap = "round"
+   smoothDomCtx.lineJoin = "round"
+   // 根据坐标花线
+   drawLines(smoothDomCtx, smoothAnimationState.pointsPos, "rgba(255,255,255,0.6)")
+   smoothAnimationState.canvasSmoothDom = smoothDom
 }
+
+/**
+ * 根据进度画线
+ */
+function drawSmoothCurveProgress(context: CanvasRenderingContext2D, pointsPos: pointsPosType, color: string) {
+   context.clearRect(0, 0, smoothAnimationState.canvasDomWith, smoothAnimationState.canvasDomHeight)
+   smoothAnimationState.canvasSmoothDom && context.drawImage(smoothAnimationState.canvasSmoothDom, 0, 0)
+   drawLines(context, pointsPos, color)
+}
+
 /**
  * 根据坐标划线
  */
-function drawSmoothCurve(context: CanvasRenderingContext2D, pointsPos: pointsPosType, progresspointsPos?: pointsPosType) {
+function drawLines(context: CanvasRenderingContext2D, pointsPos: pointsPosType, color: string) {
+   if (pointsPos.length === 0) return
    context.lineWidth = 2
-   context.lineJoin = "round" // 优化锯齿
-   context.lineCap = "round" // 优化锯齿
-   context.strokeStyle = "rgba(255,255,255,0.6)"
-   drawLines(context, pointsPos)
-   if (progresspointsPos?.length) {
-      context.strokeStyle = "#FFC121"
-      drawLines(context, progresspointsPos)
-   }
-}
-function drawLines(context: CanvasRenderingContext2D, points: pointsPosType) {
+   context.strokeStyle = color
    context.beginPath()
-   context.moveTo(points[0].x, points[0].y)
-   for (let i = 1; i < points.length - 1; i++) {
-      if (Math.abs(points[i].MeasureNumberXML - points[i - 1].MeasureNumberXML) > 1) {
-         // 取消反复和跳房子连线
-         context.stroke()
-         context.beginPath()
-         context.moveTo(points[i + 1].x, points[i + 1].y)
-         continue
-      }
-      context.lineTo(points[i].x, points[i].y)
+   context.moveTo(pointsPos[0].x, pointsPos[0].y)
+   for (let i = 1; i < pointsPos.length; i++) {
+      context.lineTo(pointsPos[i].x, pointsPos[i].y)
    }
    context.stroke()
 }

+ 34 - 26
src/state.ts

@@ -1000,13 +1000,16 @@ export const gotoNext = (note: any, skipNote?: boolean) => {
   const num = note.i;
 
   if (state.activeNoteIndex === note.i) {
-    try {
-      setCursorPosition(note, state.osmd.cursor, 'init');
-    } catch (error) {
-      console.log(error);
-    }
+    /* 没有光标了  这里就算加上光标也要性能优化 */
+    // try {
+    //   setCursorPosition(note, state.osmd.cursor, 'init');
+    // } catch (error) {
+    //   console.log(error);
+    // }
     // 重置 或者切换演奏演唱的时候 可能出现 state.activeNoteIndex === note.i的情况 执行
-    fillWordColor();
+    if(state.playState === "paused"){
+      fillWordColor();
+    }
     if (state.isSingleLine && state.playState === "paused") {
       moveSvgDom(skipNote);
     }
@@ -1035,11 +1038,12 @@ export const gotoNext = (note: any, skipNote?: boolean) => {
   } else {
     gotoCustomNote(num);
   }
-  try {
-    setCursorPosition(note, state.osmd.cursor, 'refresh');
-  } catch (error) {
-    console.log(error);
-  }
+  /* 取消光标了 */
+  // try {
+  //   setCursorPosition(note, state.osmd.cursor, 'refresh');
+  // } catch (error) {
+  //   console.log(error);
+  // }
   fillWordColor();
   // 一行谱,需要滚动小节
   if (state.isSingleLine) {
@@ -1778,22 +1782,26 @@ export const addNoteBBox = (list: any[]) => {
 }
 
 // 给歌词和音符添加动态颜色
+let prevActiveNoteIndex = -1 // 上一个激活的
 export const fillWordColor = () => {
-  // console.log('当前音符',state.activeNoteIndex)
-  state.times.forEach((item: any, idx: number) => {
-    const svgEl = document.getElementById(`vf-${state.times[idx]?.svgElement?.attrs?.id}`)
-    const stemEl = document.getElementById(`vf-${state.times[idx]?.svgElement?.attrs?.id}-stem`)
-    const stemLine = document.getElementById(`vf-${state.times[idx]?.svgElement?.attrs?.id}-lines`)
-    if ((item.i === state.activeNoteIndex || item.id === state.times[state.activeNoteIndex].id) && item.svgElement) {
-      svgEl?.classList.add('noteActive')
-      stemEl?.classList.add('noteActive')
-      stemLine?.classList.add('noteActive')
-    } else {
-      svgEl?.classList.remove('noteActive')
-      stemEl?.classList.remove('noteActive')
-      stemLine?.classList.remove('noteActive')
-    }
-  })
+  // console.log('当前音符',state.activeNoteIndex,prevActiveNoteIndex)
+  if(prevActiveNoteIndex !== -1) {
+    const prevActiveNoteId = state.times[prevActiveNoteIndex]?.svgElement?.attrs?.id
+    const svgEl = document.getElementById(`vf-${prevActiveNoteId}`)
+    const stemEl = document.getElementById(`vf-${prevActiveNoteId}-stem`)
+    const stemLine = document.getElementById(`vf-${prevActiveNoteId}-lines`)
+    svgEl?.classList.remove('noteActive')
+    stemEl?.classList.remove('noteActive')
+    stemLine?.classList.remove('noteActive')
+  }
+  const activeNoteId = state.times[state.activeNoteIndex]?.svgElement?.attrs?.id
+  const svgEl = document.getElementById(`vf-${activeNoteId}`)
+  const stemEl = document.getElementById(`vf-${activeNoteId}-stem`)
+  const stemLine = document.getElementById(`vf-${activeNoteId}-lines`)
+  svgEl?.classList.add('noteActive')
+  stemEl?.classList.add('noteActive')
+  stemLine?.classList.add('noteActive')
+  prevActiveNoteIndex = state.activeNoteIndex
 
   // 给当前匹配到的歌词添加颜色
   const currentNote = state.times[state.activeNoteIndex];

+ 4 - 10
src/view/audio-list/index.tsx

@@ -150,13 +150,7 @@ export const toggleMutePlayAudio = (source: IPlayState, muted: boolean) => {
 /** 切换节拍器音源 */
 export const changeSongSourceByBate = (isDisBate:boolean) => {
 	// isDisBate 为true 切换到不带节拍的,为false 切换到带节拍的
-	let songEleCurrentTime = audioData.songEle?.currentTime || 0
-	let backgroundEleCurrentTime = audioData.backgroundEle?.currentTime || 0
-	let mingSongEleCurrentTime = audioData.mingSongEle?.currentTime || 0
-	// 有一种场景,默认模式没有文件内容的时候,songEle,backgroundEle,mingSongEle都为空,在设置音源之前点击跳转位置,这时候以audioData.progress的时间为准
-	if(!audioData.songEle&&!audioData.backgroundEle&&!audioData.mingSongEle){
-		songEleCurrentTime = backgroundEleCurrentTime = mingSongEleCurrentTime = audioData.progress || 0
-	}
+	const currentTime = audioData.songEle?.currentTime || audioData.backgroundEle?.currentTime || audioData.mingSongEle?.currentTime || audioData.progress || 0
 	if (isDisBate) {
 		if(state.playType === "play"){
 			audioData.songEle = audioData.songCollection.songEle
@@ -177,9 +171,9 @@ export const changeSongSourceByBate = (isDisBate:boolean) => {
 			audioData.mingSongEle = audioData.songCollection.betaMingSongEle || audioData.songCollection.mingSongEle
 		}
 	}
-	audioData.songEle && (audioData.songEle.currentTime = songEleCurrentTime)
-	audioData.backgroundEle && (audioData.backgroundEle.currentTime = backgroundEleCurrentTime)
-	audioData.mingSongEle && (audioData.mingSongEle.currentTime = mingSongEleCurrentTime)
+	audioData.songEle && (audioData.songEle.currentTime = currentTime)
+	audioData.backgroundEle && (audioData.backgroundEle.currentTime = currentTime)
+	audioData.mingSongEle && (audioData.mingSongEle.currentTime = currentTime)
 	// 设置静音与取消静音
 	if (state.playSource === "music") {
 		audioData.songEle && (audioData.songEle.muted = false);

+ 1 - 0
src/view/music-score/index.tsx

@@ -80,6 +80,7 @@ export default defineComponent({
 		};
 
 		const init = async () => {
+			console.time("渲染加载耗时");
 			const container = document.getElementById("musicAndSelection");
 			if (!container || !musicData.score) return;
 			setGlobalMusicSheet();

+ 157 - 134
src/view/selection/index.tsx

@@ -255,145 +255,168 @@ export default defineComponent({
 			} catch (error) {}
 		});
 		return () => (
-			<div
-				id="selectionBox"
-				class={[
-					styles.selectionContainer,
-				]}
-				onClick={(e: Event) => e.stopPropagation()}
-			>
-				{selectData.staves.map((item: any) => {
-					// 评测得分
-					const scoreItem = item.id && evaluatingData.evaluatings[item.measureListIndex];
-					// for(let idx in evaluatingData.evaluatings) {
-					// 	const { show, measureIndex } = evaluatingData.evaluatings[idx]
-					// 	if (show && measureIndex !== item.measureListIndex) {
-					// 		evaluatingData.evaluatings[idx].show = false
-					// 	}
-					// }
-					// 高级模式下,显示节拍线
-					// 不是报告模式
-					// 不是多小节休止符
-					// 节拍线开关
-					// 当前小节
-					// 当前小节
-					const lineShow =
-						!state.isReport &&
-						metronomeData.cursorMode === 2 &&
-						item.MeasureNumberXML === metronomeData.activeMetro?.measureNumberXML &&
-						state.times[state.activeNoteIndex].MeasureNumberXML === item.MeasureNumberXML;
-					return (
-						<>
-							{item.staveBox && (
-								<div
-									class={[
-										styles.position,
-										// scoreItem ? `scoreItemLeve${scoreItem.leve}` : "", // 去掉评测小节得分的背景色
-										item.multipleRestMeasures <= 1 ? styles.staveBg : "",
-										(state.platform === IPlatform.PC && state.zoom > 0.8) ? styles.linePC : '',
-									]}
-									style={item.staveBox}
-									onClick={() => handleSelection(item)}
-								>
-									{lineShow && (
-										<div style={{height: selectData.measureHeight + 'px', position: 'relative'}}>
-											<div 
-											class={[
-												styles.line,
-												state.setting.eyeProtection ? styles.eyeLine : '',
-												state.musicRenderType == EnumMusicRenderType.staff ? styles.lineStaff : styles.lineJianPu,
-											]} 
-											style={{ left: metronomeData.activeMetro.left }}></div>
-										</div>
-									)}
-									{!state.isReport &&
-										!!item.multipleRestMeasures &&
-										state.activeMeasureIndex == item.MeasureNumberXML && (
-											<div class={styles.dotWrap}>{item.multipleRestMeasures}</div>
-										)}
-									<Transition
-										name="centerTop"
-										onAfterEnter={() => {
-											scoreItem.show = false;
-										}}
+			<>
+				<div class={styles.staveBgContainer}>
+						{
+							selectData.staves.map((item: any) => {
+								return (
+									<>
+										{
+											item.staveBox && item.multipleRestMeasures <= 1 && 
+												<div 
+													style={{
+														left:item.staveBox.left,
+														top:`calc(${item.staveBox.top} + ${item.staveBox.height})`,
+														width:item.staveBox.width
+													}}
+													class={[styles.staveBg]}
+												></div>
+										}
+									</>
+								)
+							})
+						}
+				</div>
+				<div
+					id="selectionBox"
+					class={[
+						styles.selectionContainer,
+					]}
+					onClick={(e: Event) => e.stopPropagation()}
+				>
+					{selectData.staves.map((item: any, index) => {
+						// 评测得分
+						const scoreItem = item.id && evaluatingData.evaluatings[item.measureListIndex];
+						// for(let idx in evaluatingData.evaluatings) {
+						// 	const { show, measureIndex } = evaluatingData.evaluatings[idx]
+						// 	if (show && measureIndex !== item.measureListIndex) {
+						// 		evaluatingData.evaluatings[idx].show = false
+						// 	}
+						// }
+						// 高级模式下,显示节拍线
+						// 不是报告模式
+						// 不是多小节休止符
+						// 节拍线开关
+						// 当前小节
+						// 当前小节
+						const lineShow =
+							!state.isReport &&
+							metronomeData.cursorMode === 2 &&
+							item.MeasureNumberXML === metronomeData.activeMetro?.measureNumberXML &&
+							state.times[state.activeNoteIndex].MeasureNumberXML === item.MeasureNumberXML;
+						return (
+							<>
+								{item.staveBox && (
+									<div
+										key={item.id}
+										class={[
+											styles.position,
+											// scoreItem ? `scoreItemLeve${scoreItem.leve}` : "", // 去掉评测小节得分的背景色
+											(state.platform === IPlatform.PC && state.zoom > 0.8) ? styles.linePC : '',
+										]}
+										style={item.staveBox}
+										onClick={() => handleSelection(item)}
 									>
-										{scoreItem?.show && (
-											<div
-												class={styles.scoreItem}
-												style={{ color: leveByScoreMeasureIcons[scoreItem.leve]?.color || "" }}
-											>
-												<img src={leveByScoreMeasureIcons[scoreItem.leve]?.icon} />
-												<span>{scoreItem.score}</span>
+										{lineShow && (
+											<div style={{height: selectData.measureHeight + 'px', position: 'relative'}}>
+												<div 
+												class={[
+													styles.line,
+													state.setting.eyeProtection ? styles.eyeLine : '',
+													state.musicRenderType == EnumMusicRenderType.staff ? styles.lineStaff : styles.lineJianPu,
+												]} 
+												style={{ left: metronomeData.activeMetro.left }}></div>
 											</div>
 										)}
-									</Transition>
-								</div>
-							)}
-						</>
-					);
-				})}
-				{selectData.notes.map((item: any) => {
-					return (
-						<div
-							class={[styles.position, disableClickNote.value && styles.disable, styles.note, `noteIndex_${item.index}`]}
-							style={item.bbox}
-							onClick={() => skipNotePlay(item.index)}
-						>
-							{/* <div class={styles.noteFollow} data-vf={"vf" + item.id}>
-								<Icon name="success" />
-								<Icon name="cross" />
-							</div> */}
-							<div class={styles.noteFollow} data-vf={"vf" + item.id}>
-								{/* <Icon name="success" />
-								<Icon name="cross" /> */}
-								<div class={[styles.followTipUp, 'tip-up']}>
-									<img src={IntonationUp} />
-									<span>音准<i>高了</i></span>
-								</div>
-								<div class={[styles.followTipDown, 'tip-down']}>
-									<img src={IntonationDown} />
-									<span>音准<i>低了</i></span>
-								</div>
-							</div>							
-							<div class={[styles.noteDot, 'node-dot']}></div>
-						</div>
-					);
-				})}
-				{/* 选段 */}
-				{
-					sectionPosData.value.map((item,index) =>{
+										{!state.isReport &&
+											!!item.multipleRestMeasures &&
+											state.activeMeasureIndex == item.MeasureNumberXML && (
+												<div class={styles.dotWrap}>{item.multipleRestMeasures}</div>
+											)}
+										<Transition
+											name="centerTop"
+											onAfterEnter={() => {
+												scoreItem.show = false;
+											}}
+										>
+											{scoreItem?.show && (
+												<div
+													class={styles.scoreItem}
+													style={{ color: leveByScoreMeasureIcons[scoreItem.leve]?.color || "" }}
+												>
+													<img src={leveByScoreMeasureIcons[scoreItem.leve]?.icon} />
+													<span>{scoreItem.score}</span>
+												</div>
+											)}
+										</Transition>
+									</div>
+								)}
+							</>
+						);
+					})}
+					{selectData.notes.map((item: any) => {
 						return (
-							item && <div class={styles.selectBox} style={item}>
-								<div class={[styles.selectHandle,index>0&&styles.selectHandleRight,(state.playState==="play" || query.workRecord)&&styles.playIng]} onClick={()=>{
-									// 如果选择了2个 删除左边的时候
-									if(state.section.length===2&&index === 0){
-										state.section = []
-										// 重置速度和播放倍率
-										resetBaseRate(state.activeNoteIndex);
-										showToast({
-											message: "请选择开始小节",
-											duration: 0,
-											position: "top",
-											className: "selectionToast",
-										});
-									}else{
-										state.section.splice(index,1)
-										state.section = [...state.section]  // 触发 watch
-										showToast({
-											message: state.section.length?"请选择结束小节":"请选择开始小节",
-											duration: 0,
-											position: "top",
-											className: "selectionToast",
-										});
-									}
-								}}></div>
+							<div
+								class={[styles.position, disableClickNote.value && styles.disable, styles.note, `noteIndex_${item.index}`]}
+								style={item.bbox}
+								onClick={() => skipNotePlay(item.index)}
+							>
+								{/* <div class={styles.noteFollow} data-vf={"vf" + item.id}>
+									<Icon name="success" />
+									<Icon name="cross" />
+								</div> */}
+								<div class={styles.noteFollow} data-vf={"vf" + item.id}>
+									{/* <Icon name="success" />
+									<Icon name="cross" /> */}
+									<div class={[styles.followTipUp, 'tip-up']}>
+										<img src={IntonationUp} />
+										<span>音准<i>高了</i></span>
+									</div>
+									<div class={[styles.followTipDown, 'tip-down']}>
+										<img src={IntonationDown} />
+										<span>音准<i>低了</i></span>
+									</div>
+								</div>							
+								<div class={[styles.noteDot, 'node-dot']}></div>
 							</div>
-						)
-					})
-				}
-				{/* 移动模块 */}
-				{query.isMove == "1" && <MoveMusicScore />}
-			</div>
+						);
+					})}
+					{/* 选段 */}
+					{
+						sectionPosData.value.map((item,index) =>{
+							return (
+								item && <div class={styles.selectBox} style={item}>
+									<div class={[styles.selectHandle,index>0&&styles.selectHandleRight,(state.playState==="play" || query.workRecord)&&styles.playIng]} onClick={()=>{
+										// 如果选择了2个 删除左边的时候
+										if(state.section.length===2&&index === 0){
+											state.section = []
+											// 重置速度和播放倍率
+											resetBaseRate(state.activeNoteIndex);
+											showToast({
+												message: "请选择开始小节",
+												duration: 0,
+												position: "top",
+												className: "selectionToast",
+											});
+										}else{
+											state.section.splice(index,1)
+											state.section = [...state.section]  // 触发 watch
+											showToast({
+												message: state.section.length?"请选择结束小节":"请选择开始小节",
+												duration: 0,
+												position: "top",
+												className: "selectionToast",
+											});
+										}
+									}}></div>
+								</div>
+							)
+						})
+					}
+					{/* 移动模块 */}
+					{query.isMove == "1" && <MoveMusicScore />}
+				</div>
+			</>
 		);
 	},
 });

+ 1 - 1
vite.config.ts

@@ -76,7 +76,7 @@ export default defineConfig({
         // target: "https://kt.colexiu.com",
         // target: "https://test.lexiaoya.cn",
         // target: "https://kt.colexiu.com",
-        //target: "https://test.resource.colexiu.com", // 内容平台开发环境,内容平台开发,需在url链接上加上isCbs=true
+        // target: "https://test.resource.colexiu.com", // 内容平台开发环境,内容平台开发,需在url链接上加上isCbs=true
         target: "https://test.kt.colexiu.com",
         //target: "https://mec.colexiu.com",
         changeOrigin: true,