index.tsx 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. import { defineComponent, onMounted, PropType, ref, Ref, toRefs, onUpdated, onUnmounted, watch } from 'vue'
  2. import { EngravingRules, IMusicScore, OpenSheetMusicDisplay, OSMDOptions } from '/osmd-extended/src'
  3. import { useEngravingRules, useOsmd, useOsmdLoader } from './uses'
  4. import SettingState from '/src/pages/detail/setting-state'
  5. import detailState from '/src/pages/detail/state'
  6. import event from '/src/components/music-score/event'
  7. import styles from '/src/components/music-score/index.module.less'
  8. import SectionWrap from '../pages/detail/section-box'
  9. import runtime, * as RuntimeUtils from '/src/pages/detail/runtime'
  10. import { Toast } from 'vant'
  11. import axios from 'umi-request'
  12. import { Cursor } from '../helpers/cursor'
  13. import { formatZoom } from '../helpers/utils'
  14. export type onRerenderType = (osmd?: OpenSheetMusicDisplay | null) => void
  15. export type SetRenderOptions = {
  16. score?: string
  17. }
  18. const rendered = ref(false)
  19. export default defineComponent({
  20. name: 'music-score',
  21. props: {
  22. isSoundEffect: {
  23. type: Boolean,
  24. default: false,
  25. },
  26. score: {
  27. type: String,
  28. default: '',
  29. },
  30. showPartNames: {
  31. type: Boolean,
  32. default: false,
  33. },
  34. opotions: {
  35. type: Object as PropType<OSMDOptions>,
  36. default: () => {},
  37. },
  38. EngravingRules: {
  39. type: Object as PropType<{ [P in keyof EngravingRules]?: EngravingRules[P] | undefined }>,
  40. default: () => {},
  41. },
  42. showSection: {
  43. type: Boolean,
  44. default: true,
  45. },
  46. },
  47. emits: ['rerender', 'startRender', 'renderError', 'loaddingEnd'],
  48. setup(props, { emit, expose }) {
  49. // 获取svg文件
  50. const getSvgXml = async (url: string) => {
  51. event.off('section-click', RuntimeUtils.noteClick)
  52. emit('startRender')
  53. const svg = await axios.get(url)
  54. if (container.value) {
  55. let svgDiv = document.createElement('div')
  56. svgDiv.innerHTML = svg
  57. container.value?.appendChild(svgDiv.firstElementChild!) //innerHTML = svg
  58. let svgEL = document.querySelector('#osmdSvgPage1')
  59. const svgContainer = document.getElementById('svgContainer')
  60. let width: any = svgEL?.getAttribute('width')
  61. let height: any = svgEL?.getAttribute('height')
  62. width = isNaN(Number(width)) ? 0 : Number(width)
  63. height = isNaN(Number(height)) ? 0 : Number(height)
  64. if (width && height) {
  65. let ratio = height / width
  66. let _w: any = svgContainer?.offsetWidth || document.body.clientWidth
  67. detailState.zoom = _w / width
  68. svgEL?.setAttribute('width', _w)
  69. svgEL?.setAttribute('height', _w * ratio + '')
  70. }
  71. }
  72. }
  73. const getNotePosition = (times: any[]) => {
  74. for (let i = 0; i < times.length; i++) {
  75. const item = times[i]
  76. if (item?.svgElelent?.bbox) {
  77. item.svgElelent.bbox.x *= detailState.zoom
  78. item.svgElelent.bbox.y *= detailState.zoom
  79. item.svgElelent.bbox.w *= detailState.zoom
  80. item.svgElelent.bbox.h *= detailState.zoom
  81. }
  82. if (item.noteElement?.sourceMeasure?.verticalMeasureList?.[0]?.boundingBox) {
  83. item.noteElement.sourceMeasure.verticalMeasureList[0].boundingBox.absolutePosition.x *= detailState.zoom
  84. item.noteElement.sourceMeasure.verticalMeasureList[0].boundingBox.absolutePosition.y *= detailState.zoom
  85. item.noteElement.sourceMeasure.verticalMeasureList[0].boundingBox.size.width *= detailState.zoom
  86. item.noteElement.sourceMeasure.verticalMeasureList[0].boundingBox.size.height *= detailState.zoom
  87. }
  88. if (item.noteElement?.sourceMeasure?.verticalMeasureList?.[0]?.stave) {
  89. item.noteElement.sourceMeasure.verticalMeasureList[0].stave.x *= detailState.zoom
  90. item.noteElement.sourceMeasure.verticalMeasureList[0].stave.y *= detailState.zoom
  91. item.noteElement.sourceMeasure.verticalMeasureList[0].stave.width *= detailState.zoom
  92. item.noteElement.sourceMeasure.verticalMeasureList[0].stave.height *= detailState.zoom
  93. item.noteElement.sourceMeasure.verticalMeasureList[0].stave.start_x *= detailState.zoom
  94. item.noteElement.sourceMeasure.verticalMeasureList[0].stave.end_x *= detailState.zoom
  95. }
  96. if (item?.cursorBox?.move) {
  97. console.log(detailState.zoom)
  98. // console.log("🚀 ~ item.cursorBox.x", item.cursorBox.x * detailState.zoom, item?.svgElelent?.bbox?.x)
  99. // item.cursorBox.x = item?.svgElelent?.bbox?.x ? formatZoom(item.svgElelent.bbox.x) : item.cursorBox.x * detailState.zoom
  100. item.cursorBox.x *= detailState.zoom
  101. item.cursorBox.y = item.cursorBox.y * detailState.zoom + 10
  102. item.cursorBox.w *= detailState.zoom
  103. item.cursorBox.h *= detailState.zoom
  104. }
  105. }
  106. return times
  107. }
  108. // 获取json数据
  109. const getMusicJson = async (url: string) => {
  110. const res = await axios.get(url)
  111. SettingState.sett.scoreSize = res?.osmd?.scoreSize || 'middle'
  112. if (res && Array.isArray(res.times)) {
  113. detailState.times = getNotePosition(res.times) // res.times
  114. if (detailState.times?.[0]?.cursorBox?.move) {
  115. // 初始化cursor
  116. res.osmd.cursor = new Cursor({ ...detailState.times?.[0]?.cursorBox })
  117. // console.log("🚀 ~ res.osmd.cursor", res.osmd.cursor)
  118. container.value?.appendChild(res.osmd.cursor.img)
  119. }
  120. detailState.renderType = 'cache'
  121. emit('rerender', res.osmd)
  122. runtime.isFirstPlay = false
  123. event.on('section-click', RuntimeUtils.noteClick)
  124. }
  125. }
  126. const { EngravingRules, opotions, showPartNames, score } = toRefs(props)
  127. const container: Ref<HTMLDivElement | undefined> = ref()
  128. let osmd: Ref<OpenSheetMusicDisplay | undefined> = ref()
  129. const setOsdm = () => {
  130. event.off('section-click', RuntimeUtils.noteClick)
  131. emit('startRender')
  132. osmd.value = useOsmd(
  133. container,
  134. { ...opotions.value, drawPartNames: showPartNames.value },
  135. EngravingRules.value
  136. ).value
  137. if (osmd.value) {
  138. useEngravingRules(osmd.value, EngravingRules.value)
  139. runtime.isFirstPlay = false
  140. event.on('section-click', RuntimeUtils.noteClick)
  141. }
  142. }
  143. // 直接读取渲染数据
  144. const productJsonAndSvg = async (renderData: any) => {
  145. await getSvgXml(renderData.svg)
  146. getMusicJson(renderData.json)
  147. }
  148. onMounted(async () => {
  149. if (rendered.value) return
  150. // console.log( SettingState.sett.keySignature, detailState.activeDetail)
  151. if (
  152. (SettingState.sett.type === 'staff' && !detailState?.activeDetail?.musicSvg) || // 没有五线谱缓存数据
  153. (SettingState.sett.type === 'jianpu' &&
  154. SettingState.sett.keySignature &&
  155. !detailState?.activeDetail?.musicFirstSvg) || // 没有固定调缓存数据
  156. (SettingState.sett.type === 'jianpu' &&
  157. !SettingState.sett.keySignature &&
  158. !detailState?.activeDetail?.musicJianSvg) // 没有首调缓存数据
  159. ) {
  160. setOsdm()
  161. return
  162. }
  163. let renderData: any = null
  164. try {
  165. if (SettingState.sett.type === 'staff' && detailState.activeDetail?.musicSvg) {
  166. renderData = JSON.parse(detailState.activeDetail.musicSvg)
  167. } else {
  168. if (SettingState.sett.keySignature && detailState.activeDetail?.musicFirstSvg) {
  169. //固定调
  170. renderData = JSON.parse(detailState.activeDetail.musicFirstSvg)
  171. } else {
  172. // 首调
  173. if (detailState.activeDetail?.musicJianSvg) {
  174. renderData = JSON.parse(detailState.activeDetail.musicJianSvg)
  175. }
  176. }
  177. }
  178. } catch (error) {
  179. console.error(error)
  180. detailState.renderType = 'native'
  181. }
  182. if (renderData && !props.isSoundEffect) {
  183. productJsonAndSvg(renderData)
  184. } else {
  185. setOsdm()
  186. }
  187. })
  188. onUnmounted(() => {
  189. event.off('section-click', RuntimeUtils.noteClick)
  190. })
  191. watch(
  192. [score, osmd],
  193. async () => {
  194. if (osmd && osmd.value && score.value) {
  195. try {
  196. emit('startRender')
  197. await useOsmdLoader(osmd.value, score.value)
  198. emit('rerender', osmd.value)
  199. event.emit('loaded')
  200. resetFormate()
  201. } catch (error) {
  202. console.error(error)
  203. emit('renderError')
  204. }
  205. }
  206. },
  207. { immediate: true }
  208. )
  209. /**
  210. * 重新渲染曲谱方法
  211. * @param {SetRenderOptions} options
  212. */
  213. const setRender = async ({ score: s }: SetRenderOptions = {}) => {
  214. const renderScore = s || score.value
  215. // console.log(renderScore)
  216. // Toast('加载中,请稍后...')
  217. setTimeout(async () => {
  218. if (osmd && osmd.value && renderScore) {
  219. await osmd.value.clear()
  220. setOsdm()
  221. try {
  222. detailState.section = []
  223. detailState.sectionStatus = false
  224. await useOsmdLoader(osmd.value, renderScore)
  225. emit('rerender', osmd.value)
  226. event.emit('loaded')
  227. resetFormate()
  228. } catch (error) {
  229. console.error(error)
  230. emit('renderError')
  231. }
  232. }
  233. }, 100)
  234. }
  235. const reRender = async () => {
  236. // if (detailState.renderType == 'cache'){
  237. // detailState.times = getNotePosition(detailState.times)
  238. // }
  239. await osmd.value?.render()
  240. }
  241. const resetFormate = () => {
  242. const stafflines: SVGAElement[] = Array.from((container.value as HTMLElement).querySelectorAll('.staffline'))
  243. const baseStep = 4 // 两个元素相间,的间距
  244. for (let i = 0, len = stafflines.length; i < len; i++) {
  245. const staffline = stafflines[i]
  246. const stafflineBox = staffline.getBBox()
  247. const stafflineCenter = stafflineBox.y + stafflineBox.height / 2
  248. const vfmeasures: SVGAElement[] = Array.from(staffline.querySelectorAll('.vf-measure'))
  249. const vfcurve: SVGAElement[] = Array.from(staffline.querySelectorAll('.vf-curve'))
  250. const vfvoices: SVGAElement[] = Array.from(staffline.querySelectorAll('.vf-measure > .vf-voices'))
  251. const vfbeams: SVGAElement[] = Array.from(staffline.querySelectorAll('.vf-measure > .vf-beams'))
  252. const vfties: SVGAElement[] = Array.from(staffline.querySelectorAll('.vf-ties'))
  253. const vflines: SVGAElement[] = Array.from(staffline.querySelectorAll('.vf-line'))
  254. const texts: SVGAElement[] = Array.from(staffline.querySelectorAll('.vf-measure > .vf-stave text'))
  255. const rects: SVGAElement[] = Array.from(staffline.querySelectorAll('.vf-measure > .vf-stave rect[fill=none]'))
  256. const staveSection: SVGAElement[] = Array.from(staffline.querySelectorAll('.vf-measure .vf-staveSection'))
  257. // 反复标记 和 小节碰撞
  258. const repetWord = ['To Coda', 'D.S. al Coda', 'Coda']
  259. texts
  260. .filter((n) => repetWord.includes(n.textContent || ''))
  261. .forEach((t) => {
  262. vfbeams.forEach((curve) => {
  263. const result = collisionDetection(t, curve)
  264. const prePath: SVGAElement = t?.previousSibling as unknown as SVGAElement
  265. if (result.isCollision) {
  266. const shift_y = Number(t.getAttribute('y')) - (result.b1 - result.t2) - baseStep + ''
  267. t.setAttribute('y', shift_y)
  268. if (
  269. prePath &&
  270. prePath.getAttribute('stroke-width') === '0.3' &&
  271. prePath.getAttribute('stroke') === 'none' &&
  272. (prePath.getAttribute('d')?.length || 0) > 3000
  273. ) {
  274. prePath.style.transform = `translateY(${-(result.b1 - result.t2 + baseStep)}px)`
  275. }
  276. }
  277. })
  278. vfvoices.forEach((curve) => {
  279. const result = collisionDetection(t, curve)
  280. const prePath: SVGAElement = t?.previousSibling as unknown as SVGAElement
  281. if (result.isCollision) {
  282. const shift_y = Number(t.getAttribute('y')) - (result.b1 - result.t2) - baseStep + ''
  283. t.setAttribute('y', shift_y)
  284. if (
  285. prePath &&
  286. prePath.getAttribute('stroke-width') === '0.3' &&
  287. prePath.getAttribute('stroke') === 'none' &&
  288. (prePath.getAttribute('d')?.length || 0) > 3000
  289. ) {
  290. prePath.style.transform = `translateY(${-(result.b1 - result.t2 + baseStep)}px)`
  291. }
  292. }
  293. })
  294. })
  295. // 文字方框和飞线碰撞
  296. // console.log(staveSection, 'staveSection out')
  297. staveSection.forEach((t) => {
  298. let shift_y = 0
  299. ;[...vfcurve, ...vfties, ...vfvoices].forEach((curve) => {
  300. const result = collisionDetection(t, curve)
  301. // console.log(result, curve)
  302. if (result.isCollision) {
  303. shift_y = Math.min(shift_y, result.t2 - result.b1 - baseStep)
  304. }
  305. })
  306. t.style.transform = `translateY(${shift_y}px)`
  307. })
  308. // 文字和小节碰撞
  309. let vftexts = Array.from(staffline.querySelectorAll('.vf-text > text')).filter(
  310. (n: any) => n.getBBox().y < stafflineCenter
  311. )
  312. for (let i = 0; i < vftexts.length; i++) {
  313. const _text = vftexts[i]
  314. for (let j = 0; j < vftexts.length; j++) {
  315. if (_text.parentNode === vftexts[j].parentNode) continue
  316. const result = collisionDetection(_text as SVGAElement, vftexts[j] as SVGAElement)
  317. if (result.isCollision) {
  318. if (_text.textContent === vftexts[j].textContent) {
  319. vftexts[j].parentNode?.removeChild(vftexts[j])
  320. continue
  321. }
  322. }
  323. }
  324. }
  325. vftexts = Array.from(staffline.querySelectorAll('.vf-text > text')).filter(
  326. (n: any) => n.getBBox().y < stafflineCenter
  327. )
  328. let maxY = 0
  329. let _vftexts: SVGAElement[] = []
  330. vftexts.forEach((vftext: any) => {
  331. const textBox = vftext.getBBox()
  332. if (textBox.y < stafflineCenter) {
  333. maxY = Math.max(maxY, textBox.y + textBox.height)
  334. _vftexts.push(vftext as SVGAElement)
  335. }
  336. })
  337. if (maxY !== 0 && _vftexts.length > 1) {
  338. _vftexts.forEach((vftext) => {
  339. vftext.setAttribute('y', maxY + '')
  340. })
  341. }
  342. vftexts.forEach((vftext) => {
  343. ;[...vfcurve, ...vfmeasures, ...vflines].forEach((vfmeasure) => {
  344. let result = collisionDetection(vftext as SVGAElement, vfmeasure)
  345. if (result.isCollision && result.b1 < result.b2 && result.t1 < result.b2 - (result.b2 - result.t2) / 2) {
  346. const shift_y = Number(vftext.getAttribute('y')) - (result.b1 - result.t2) - baseStep + ''
  347. vftext.setAttribute('y', shift_y)
  348. }
  349. })
  350. })
  351. vftexts.forEach((vftext) => {
  352. vftexts.forEach((text) => {
  353. if (
  354. vftext.parentNode !== text.parentNode &&
  355. !['marcato', 'legato'].includes(vftext.textContent as string)
  356. ) {
  357. if (['marcato', 'legato'].includes(text.textContent as string)) {
  358. const result = collisionDetection(vftext as SVGAElement, text as SVGAElement, 30, 30)
  359. if (result.isCollision) {
  360. const textBBox = (vftext as SVGAElement).getBBox()
  361. text.setAttribute('x', textBBox.x + textBBox.width + 5 + '')
  362. text.setAttribute('y', textBBox.y + textBBox.height - 5 + '')
  363. }
  364. } else {
  365. const result = collisionDetection(vftext as SVGAElement, text as SVGAElement)
  366. if (result.isCollision) {
  367. const _y = Number(vftext.getAttribute('y'))
  368. const shift_y = result.b2 - result.t2 < 24 ? 24 : result.b2 - result.t2
  369. text.setAttribute('y', _y - shift_y - 0.5 + '')
  370. }
  371. }
  372. }
  373. })
  374. })
  375. const vftextBottom = Array.from(staffline.querySelectorAll('.vf-text > text')).filter(
  376. (n: any) => n.getBBox().y > stafflineCenter
  377. )
  378. const vflineBottom = Array.from(staffline.querySelectorAll('.vf-line')).filter(
  379. (n: any) => n.getBBox().y > stafflineCenter
  380. )
  381. // 去重
  382. for (let i = 0; i < vftextBottom.length; i++) {
  383. const _text = vftextBottom[i]
  384. for (let j = 0; j < vftextBottom.length; j++) {
  385. if (_text.parentNode === vftextBottom[j].parentNode) continue
  386. const result = collisionDetection(_text as SVGAElement, vftextBottom[j] as SVGAElement)
  387. if (result.isCollision) {
  388. if (_text.textContent === vftextBottom[j].textContent) {
  389. vftextBottom[j].parentNode?.removeChild(vftextBottom[j])
  390. continue
  391. }
  392. }
  393. }
  394. }
  395. // 1,2线谱底部文字重叠问题
  396. vftextBottom.forEach((vftext) => {
  397. ;[...vfmeasures].forEach((n) => {
  398. let result = collisionDetection(vftext as SVGAElement, n)
  399. if (result.isCollision) {
  400. vftext.setAttribute('y', result.b2 + Math.abs(result.t1 - Number(vftext.getAttribute('y'))) + '')
  401. }
  402. })
  403. })
  404. // 如果渐弱渐强有平行的文字
  405. vflineBottom.forEach((line) => {
  406. const texts: any[] = []
  407. if (line.nextElementSibling?.classList.contains('vf-line')) {
  408. vftextBottom.forEach((text) => {
  409. let result = collisionDetection(line as SVGAElement, text as SVGAElement, 20, 20)
  410. if (result.isCollision) {
  411. texts.push({
  412. text: text as SVGAElement,
  413. result,
  414. })
  415. }
  416. })
  417. }
  418. if (texts.length === 1) {
  419. const result = texts[0].result
  420. const text = texts[0].text
  421. if (result.x2 + result.w2 < result.x1) {
  422. // 左
  423. if (Math.abs(result.y2 - result.y1) > 10) {
  424. text.setAttribute('y', result.y1 + result.h2 / 2 + '')
  425. }
  426. } else if (result.x2 > result.x1 + result.w1) {
  427. // 右
  428. if (Math.abs(result.y2 - result.y1) > 10) {
  429. text.setAttribute('y', result.y1 + result.h2 / 2 + '')
  430. }
  431. } else {
  432. if (Math.abs(result.x2 - result.x1) < Math.abs(result.x2 + result.w2 - result.x1 - result.w1)) {
  433. // console.log(text, '有交集', '靠左')
  434. text.setAttribute('x', result.x1 - result.w2 - 5 + '')
  435. if (Math.abs(result.y2 - result.y1) > 10) {
  436. text.setAttribute('y', result.y1 + result.h2 / 2 + '')
  437. }
  438. } else {
  439. // console.log(text, '有交集', '靠右')
  440. text.setAttribute('x', result.x1 + result.w1 + 5 + '')
  441. if (Math.abs(result.y2 - result.y1) > 10) {
  442. text.setAttribute('y', result.y1 + result.h2 / 2 + '')
  443. }
  444. }
  445. }
  446. } else if (texts.length === 2) {
  447. const result1 = texts[0].result
  448. const text1 = texts[0].text
  449. const result2 = texts[1].result
  450. const text2 = texts[1].text
  451. text1.setAttribute('x', result1.x1 - result1.w2 - 5 + '')
  452. if (Math.abs(result1.y2 - result1.y1) > 10) {
  453. text1.setAttribute('y', result1.y1 + result1.h2 / 2 + '')
  454. }
  455. text2.setAttribute('x', result2.x1 + result2.w1 + 5 + '')
  456. if (Math.abs(result2.y2 - result2.y1) > 10) {
  457. text2.setAttribute('y', result2.y1 + result2.h2 / 2 + '')
  458. }
  459. } else if (texts.length === 3) {
  460. // console.log(texts)
  461. }
  462. })
  463. vftextBottom.forEach((vftext) => {
  464. vftextBottom.forEach((text) => {
  465. if (
  466. vftext.parentNode !== text.parentNode &&
  467. !['marcato', 'legato', 'cresc.', 'Cantabile'].includes(vftext.textContent as string)
  468. ) {
  469. if (['marcato', 'legato', 'cresc.', 'Cantabile'].includes(text.textContent as string)) {
  470. const result = collisionDetection(vftext as SVGAElement, text as SVGAElement, 30, 30)
  471. if (result.isCollision) {
  472. const textBBox = (vftext as SVGAElement).getBBox()
  473. text.setAttribute('x', textBBox.x + textBBox.width + 5 + '')
  474. text.setAttribute('y', textBBox.y + textBBox.height - 5 + '')
  475. }
  476. } else {
  477. const result = collisionDetection(vftext as SVGAElement, text as SVGAElement)
  478. if (result.isCollision) {
  479. text.setAttribute('y', result.y1 + result.h1 + result.h2 + '')
  480. }
  481. }
  482. }
  483. })
  484. })
  485. }
  486. setTimeout(() => resetGlobalText())
  487. }
  488. // 技巧文本
  489. const resetGlobalText = () => {
  490. if (!container.value) return
  491. const svg = container.value.querySelector('svg')
  492. if (!svg) return
  493. const svgBBox = svg.getBBox()
  494. let vfstavetempo: SVGAElement[] = Array.from(container.value.querySelectorAll('.vf-stavetempo')).reduce(
  495. (eles: SVGAElement[], value: any) => {
  496. if (eles.find((n) => n.outerHTML === value.outerHTML)) value?.parentNode?.removeChild(value)
  497. else eles.push(value)
  498. return eles
  499. },
  500. []
  501. )
  502. const staffline: SVGAElement[] = Array.from(container.value.querySelectorAll('.staffline'))
  503. const vfmeasures: SVGAElement[] = Array.from(container.value.querySelectorAll('.staffline > .vf-measure'))
  504. const vftexts: SVGAElement[] = Array.from(container.value.querySelectorAll('.staffline > .vf-text'))
  505. const vfcurves: SVGAElement[] = Array.from(container.value.querySelectorAll('.staffline > .vf-curve'))
  506. vfstavetempo.forEach((child: SVGAElement) => {
  507. let _y = 0
  508. ;[...vfmeasures, ...vftexts, ...vfcurves].forEach((ele) => {
  509. const result = collisionDetection(child as SVGAElement, ele)
  510. if (result.isCollision && (result.b1 < result.b2 || result.r1 > result.l2 || result.l1 < result.r2)) {
  511. _y = Math.min(_y, result.t2 - result.b1)
  512. }
  513. })
  514. if (_y !== 0) {
  515. child.style.transform = `translateY(${_y}px)`
  516. }
  517. const childBBox = child.getBBox()
  518. const rightY = (childBBox.x + childBBox.width) * 0.7 - Number(svg.getAttribute('width'))
  519. if (rightY > 0) {
  520. ;[...staffline, ...vfstavetempo].forEach((tempo) => {
  521. if (child != tempo) {
  522. const result = collisionDetection(child as SVGAElement, tempo, Math.abs(rightY), Math.abs(_y))
  523. if (result.isCollision) {
  524. _y = result.t2 - result.b1
  525. }
  526. }
  527. })
  528. child.style.transform = `translate(-${rightY / 0.7}px,${_y}px)`
  529. }
  530. })
  531. if (svgBBox.y < 0) {
  532. svg.setAttribute('height', Number(svg.getAttribute('height')) - svgBBox.y + 10 + '')
  533. }
  534. }
  535. // 碰撞检测
  536. const collisionDetection = (a: SVGAElement, b: SVGAElement, distance: number = 0, distance_y: number = 0) => {
  537. const abbox = a.getBBox()
  538. const bbbox = b.getBBox()
  539. let t1 = abbox.y - distance_y
  540. let l1 = abbox.x - distance
  541. let r1 = abbox.x + abbox.width + distance
  542. let b1 = abbox.y + abbox.height + distance_y
  543. let t2 = bbbox.y
  544. let l2 = bbbox.x
  545. let r2 = bbbox.x + bbbox.width
  546. let b2 = bbbox.y + bbbox.height
  547. if (b1 < t2 || l1 > r2 || t1 > b2 || r1 < l2) {
  548. // 表示没碰上
  549. return {
  550. isCollision: false,
  551. t1,
  552. l1,
  553. r1,
  554. b1,
  555. t2,
  556. l2,
  557. r2,
  558. b2,
  559. x1: abbox.x,
  560. y1: abbox.y,
  561. x2: bbbox.x,
  562. y2: bbbox.y,
  563. h1: abbox.height,
  564. h2: bbbox.height,
  565. w1: abbox.width,
  566. w2: bbbox.width,
  567. }
  568. } else {
  569. return {
  570. isCollision: true,
  571. t1,
  572. l1,
  573. r1,
  574. b1,
  575. t2,
  576. l2,
  577. r2,
  578. b2,
  579. x1: abbox.x,
  580. y1: abbox.y,
  581. x2: bbbox.x,
  582. y2: bbbox.y,
  583. h1: abbox.height,
  584. h2: bbbox.height,
  585. w1: abbox.width,
  586. w2: bbbox.width,
  587. }
  588. }
  589. }
  590. expose({
  591. setRender,
  592. reRender,
  593. })
  594. return () => {
  595. return (
  596. <div
  597. id="svgContainer"
  598. class={styles.container}
  599. style={{ position: 'relative' }}
  600. >
  601. <div ref={container}></div>
  602. {/* 小节覆盖层 */}
  603. {props.showSection && (
  604. <SectionWrap
  605. style={{
  606. position: 'absolute',
  607. width: '100%',
  608. height: '100%',
  609. zIndex: 1,
  610. top: 0,
  611. left: 0,
  612. }}
  613. />
  614. )}
  615. </div>
  616. )
  617. }
  618. },
  619. })