123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831 |
- <template>
- <div class="Metronome">
- <div class="MetronomeBox">
- <div class="headTools">
- <img
- src="./imgs/minMet.png"
- @click="
- () => {
- emits('windowMet');
- }
- "
- />
- <img
- src="./imgs/closeMet.png"
- @click="
- () => {
- emits('closeMet');
- }
- "
- />
- </div>
- <div class="beatWrap">
- <div
- class="dot"
- ref="dotDom"
- @mousedown="handleDotMousedown"
- @touchstart="handleDotTouchstart"
- ></div>
- <div class="round"></div>
- <canvas
- ref="mycanvasDom"
- class="mycanvas"
- width="252"
- height="252"
- @click="handleEventCanvas"
- ></canvas>
- <div class="beatCon">
- <img
- class="optImg"
- src="./imgs/jia.png"
- @click="
- () => {
- if (speedNum < 200) {
- speedNum++;
- }
- }
- "
- />
- <div class="optMid">
- <img class="optImg" src="./imgs/yin.png" />
- <n-input-number
- :disabled="true"
- placeholder=""
- :value="speedNum"
- :show-button="false"
- @update:value="(num:number)=>{
- if(num){
- if(num<=50){
- speedNum=50
- }else if(num>=200){
- speedNum=200
- }else{
- speedNum=num
- }
- }
- }"
- />
- </div>
- <img
- class="optImg"
- src="./imgs/jian.png"
- @click="
- () => {
- if (speedNum > 50) {
- speedNum--;
- }
- }
- "
- />
- </div>
- </div>
- <div class="selectCon">
- <n-select
- v-model:value="beatVal"
- :options="beatValOpt"
- :show-checkmark="false"
- :show-arrow="false"
- :placement="'top'"
- :render-tag="
- ({ option }:any) => {
- return h('div',{class:'beatName'},[h('div',option.labelHide),h('div','拍')])
- }
- "
- :render-label="
- (option:any) => {
- return h('div', option.labelHide);
- }
- "
- ></n-select>
- <n-select
- class="beatSymbolSel"
- v-model:value="beatSymbol"
- :options="beatSymbolOpt"
- :show-checkmark="false"
- :show-arrow="false"
- :placement="'top'"
- :render-tag="
- ({ option }:any) => {
- return h('div', { class: `beatSymbolImg${option.labelHide} beatSymbolImg` });
- }
- "
- :render-label="
- (option:any) => {
- return h('div', { class: `beatSymbolImg${option.labelHide} beatSymbolImg` });
- }
- "
- ></n-select>
- </div>
- <div class="sliderList">
- <img src="./imgs/sound.png" alt="" />
- <NSlider v-model:value="volumeNum" :step="1" :tooltip="false">
- <template #thumb>
- <img class="thumbDot" src="./imgs/dotBtn.png" alt="" />
- </template>
- </NSlider>
- <span class="sliderText">{{ volumeNum }}</span>
- </div>
- <div v-if="playState === 'pause'" class="playBtn" @click="startPlay">
- <span>播放</span>
- <img src="./imgs/playtBtn.png" />
- </div>
- <div v-else class="playBtn" @click="pausePlay">
- <span>暂停</span>
- <img class="pauseImg" src="./imgs/pauseBtn.png" />
- </div>
- </div>
- <Dragbom></Dragbom>
- </div>
- </template>
- <script setup lang="ts">
- import { ref, reactive, onMounted, onUnmounted, h, watch } from 'vue';
- import { NInputNumber, NSelect, NSlider } from 'naive-ui';
- import useMetronome from './useMetronome';
- import Dragbom from '@/hooks/useDrag/dragbom';
- import { getCachePos, setCachePos } from './useMetronome';
- import { useUserStore } from '@/store/modules/users';
- const users = useUserStore();
- const emits = defineEmits<{
- (e: 'windowMet'): void;
- (e: 'closeMet'): void;
- (e: 'playStateChange', state: 'play' | 'pause'): void;
- }>();
- /* dom */
- const mycanvasDom = ref<HTMLCanvasElement>();
- const dotDom = ref<HTMLElement>();
- /* select */
- const beatVal = ref('1');
- const beatValOpt = [
- {
- label: '',
- labelHide: '0',
- value: '0',
- class: 'beatValOptItem'
- },
- {
- label: '',
- labelHide: '1',
- value: '1',
- class: 'beatValOptItem'
- },
- {
- label: '',
- labelHide: '2',
- value: '1-1',
- class: 'beatValOptItem'
- },
- {
- label: '',
- labelHide: '3',
- value: '1-1-1',
- class: 'beatValOptItem'
- },
- {
- label: '',
- labelHide: '4',
- value: '1-1-1-1',
- class: 'beatValOptItem'
- },
- {
- label: '',
- labelHide: '5',
- value: '1-1-1-1-1',
- class: 'beatValOptItem'
- },
- {
- label: '',
- labelHide: '6',
- value: '1-1-1-1-1-1',
- class: 'beatValOptItem'
- },
- {
- label: '',
- labelHide: '7',
- value: '1-1-1-1-1-1-1',
- class: 'beatValOptItem'
- },
- {
- label: '',
- labelHide: '8',
- value: '1-1-1-1-1-1-1-1',
- class: 'beatValOptItem'
- },
- {
- label: '',
- labelHide: '9',
- value: '1-1-1-1-1-1-1-1-1',
- class: 'beatValOptItem'
- }
- ];
- const beatSymbol = ref('1');
- const beatSymbolOpt = [
- {
- label: '',
- labelHide: '1',
- value: '1',
- class: 'beatSymbolOptItem'
- },
- {
- label: '',
- labelHide: '2',
- value: '0.5-0.5',
- class: 'beatSymbolOptItem'
- },
- {
- label: '',
- labelHide: '3',
- value: '0.3333333-0.3333333-0.3333333',
- class: 'beatSymbolOptItem'
- },
- {
- label: '',
- labelHide: '4',
- value: '0.25-0.25-0.25-0.25',
- class: 'beatSymbolOptItem'
- },
- {
- label: '',
- labelHide: '5',
- value: '0.6666666-0.3333333',
- class: 'beatSymbolOptItem'
- },
- {
- label: '',
- labelHide: '6',
- value: '0.75-0.25',
- class: 'beatSymbolOptItem'
- },
- {
- label: '',
- labelHide: '7',
- value: '0.5-0.25-0.25',
- class: 'beatSymbolOptItem'
- }
- ];
- const { volumeNum, playState, speedNum, startPlay, pausePlay } = useMetronome(
- beatVal,
- beatSymbol
- );
- onMounted(() => {
- const cachePos = getCachePos(users.info.id);
- if (cachePos) {
- beatVal.value = cachePos.beatVal;
- beatSymbol.value = cachePos.beatSymbol;
- volumeNum.value = cachePos.volumeNum;
- speedNum.value = cachePos.speedNum;
- }
- /* n-input-number 有bug input-props设置maxlength不上去 */
- const iptDoms = document.querySelector(
- '.Metronome .MetronomeBox .optMid .n-input__input-el'
- );
- iptDoms?.setAttribute('maxlength', '3');
- getCircleBar(speedToScalc(speedNum.value));
- });
- onUnmounted(() => {
- setCachePos(users.info.id, {
- beatVal: beatVal.value,
- beatSymbol: beatSymbol.value,
- volumeNum: volumeNum.value,
- speedNum: speedNum.value
- });
- });
- watch(playState, () => {
- emits('playStateChange', playState.value);
- });
- watch(speedNum, () => {
- if (playState.value === 'play') {
- pausePlay();
- }
- getCircleBar(speedToScalc(speedNum.value));
- });
- watch([beatVal, beatSymbol], () => {
- if (playState.value === 'play') {
- pausePlay();
- }
- });
- function speedToScalc(speed: number) {
- return ((speed - 50) / 150) * 100;
- }
- function scalcToSpeed(scalc: number) {
- return Math.round((scalc * 150) / 100 + 50);
- }
- const handleEventCanvas = (e: MouseEvent | TouchEvent) => {
- const circle = mycanvasDom.value!.getBoundingClientRect();
- const centerX = circle.left + circle.width / 2;
- const centerY = circle.top + circle.height / 2;
- const event = isTouchEvent(e) ? e.touches[0] : e;
- const angle = Math.atan2(event.clientY - centerY, event.clientX - centerX);
- const percentage = (angle * (180 / Math.PI) + 180) / 360;
- const scalc = Math.round(percentage * 100) - 25;
- speedNum.value = scalcToSpeed(scalc < 0 ? 100 + scalc : scalc);
- };
- function handleDotMousedown() {
- function onMouseup() {
- document.removeEventListener('mousemove', onMousemove);
- document.removeEventListener('mouseup', onMouseup);
- }
- document.addEventListener('mousemove', onMousemove);
- document.addEventListener('mouseup', onMouseup);
- }
- function handleDotTouchstart() {
- function onTouchend() {
- document.removeEventListener('touchmove', onTouchmove);
- document.removeEventListener('touchend', onTouchend);
- }
- document.addEventListener('touchmove', onTouchmove);
- document.addEventListener('touchend', onTouchend);
- }
- function onMousemove(e: MouseEvent) {
- handleEventCanvas(e);
- }
- function onTouchmove(e: TouchEvent) {
- handleEventCanvas(e);
- }
- // 根据百分比画圆
- function getCircleBar(steps: number) {
- const radius = 121; //半径
- const colorList = ['#51DBFF', '#009FFF'];
- const mycanvas = mycanvasDom.value!;
- const ctx = mycanvas.getContext('2d')!;
- // 每次进来清空画布
- ctx.clearRect(0, 0, mycanvas.width, mycanvas.height);
- //找到画布的中心点
- const canvasX = mycanvas.width / 2;
- const canvasY = mycanvas.height / 2;
- //进度条是100%,所以要把一圈360度分成100份
- const progress = (Math.PI * 2) / 100;
- const ao = -Math.PI / 2 + steps * progress;
- const x1 = radius + Math.cos(ao) * radius;
- const y1 = radius + Math.sin(ao) * radius;
- // 这里开始算坐标
- dotDom.value!.style.left = x1 - 7 + 'Px';
- dotDom.value!.style.top = y1 - 7 + 'Px';
- // 绘制渐变色
- const gradientDirection = {
- x1: 0,
- y1: 0,
- x2: radius * 2, // 半径*2
- y2: 0
- };
- gradientDirection.y2 = radius * 2;
- const gradient = ctx.createLinearGradient(
- gradientDirection.x1,
- gradientDirection.y1,
- gradientDirection.x2,
- gradientDirection.y2
- );
- colorList.map((color, index) => {
- gradient.addColorStop(index, color);
- });
- ctx.strokeStyle = gradient;
- ctx.lineWidth = 10;
- ctx.save();
- ctx.beginPath();
- ctx.arc(
- canvasX,
- canvasY,
- radius,
- -Math.PI / 2,
- -Math.PI / 2 + steps * progress,
- false
- );
- ctx.stroke();
- ctx.closePath();
- ctx.restore();
- }
- function isTouchEvent(e: MouseEvent | TouchEvent): e is TouchEvent {
- return window.TouchEvent && e instanceof window.TouchEvent;
- }
- defineExpose({
- startPlay,
- pausePlay,
- speedNum,
- beatSymbol
- });
- </script>
- <style lang="less" scoped>
- .Metronome {
- width: 565Px;
- height: 554Px;
- background: url('./imgs/bg.png') no-repeat;
- background-size: 100% 100%;
- padding: 26Px 26Px 0;
- &:deep(.bom_drag) {
- position: absolute;
- bottom: 0;
- left: 0;
- height: 76Px;
- padding: 0 28Px;
- & > div {
- width: 40Px;
- height: 40Px;
- border-radius: 0 0 18Px 0;
- &:first-child {
- border-radius: 0 0 0 18Px;
- }
- }
- }
- .MetronomeBox {
- width: 100%;
- position: relative;
- display: flex;
- flex-direction: column;
- align-items: center;
- z-index: 2;
- }
- .headTools {
- position: absolute;
- right: 20Px;
- top: 18Px;
- & > img {
- margin-left: 18Px;
- width: 24Px;
- height: 24Px;
- cursor: pointer;
- }
- }
- .beatWrap {
- margin-top: 30Px;
- width: 252Px;
- height: 252Px;
- position: relative;
- display: flex;
- justify-content: center;
- align-items: center;
- .dot {
- position: absolute;
- width: 24Px;
- height: 24Px;
- background: url('./imgs/dot.png') no-repeat;
- background-size: 100% 100%;
- cursor: pointer;
- z-index: 3;
- }
- .round {
- position: absolute;
- left: 0;
- top: 0;
- width: 100%;
- height: 100%;
- border-radius: 50%;
- border: 10Px solid #d1d1d1;
- z-index: 1;
- }
- .mycanvas {
- position: absolute;
- left: 0;
- top: 0;
- z-index: 2;
- }
- .beatCon {
- border-radius: 50%;
- z-index: 4;
- width: 200Px;
- height: 200Px;
- background: url('./imgs/yuan.png') no-repeat;
- background-size: 100% 100%;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- .optMid {
- margin: 5Px 0;
- display: flex;
- align-items: center;
- :deep(.n-input-number) {
- width: 74Px;
- height: 52Px;
- margin-left: 4Px;
- .n-input {
- height: 100%;
- --n-height: 100% !important;
- --n-color-focus: initial !important;
- --n-color: initial !important;
- --n-padding-left: initial !important;
- --n-padding-right: initial !important;
- --n-color-disabled: initial !important;
- --n-caret-color: #3a3939 !important;
- .n-input__border,
- .n-input__state-border {
- display: none;
- }
- .n-input__input-el {
- text-align: center;
- font-family: DINA !important;
- font-weight: bold !important;
- font-size: 45Px !important;
- color: #000000 !important;
- }
- &.n-input--disabled {
- cursor: initial;
- .n-input__input-el {
- cursor: initial;
- }
- }
- }
- }
- }
- .optImg {
- cursor: pointer;
- width: 35Px;
- height: 35Px;
- }
- }
- }
- .selectCon {
- display: flex;
- margin-top: 22Px;
- :deep(.n-select) {
- width: 130Px;
- .n-base-selection--selected {
- --n-height: 48Px !important;
- --n-border-radius: 24Px !important;
- .n-base-selection__border,
- .n-base-selection__state-border {
- display: none;
- }
- .n-base-selection-input {
- padding: 0;
- .n-base-selection-input__content {
- .beatName {
- display: flex;
- justify-content: center;
- align-items: center;
- padding-top: 2Px;
- & > div:first-child {
- font-weight: 500;
- font-size: 30Px;
- color: #333333;
- }
- & > div:last-child {
- margin-left: 2Px;
- font-weight: 500;
- font-size: 14Px;
- color: #333333;
- margin-top: 4Px;
- }
- }
- }
- }
- }
- }
- :deep(.beatSymbolSel.n-select) {
- margin-left: 15Px;
- .n-base-selection-input__content {
- display: flex;
- justify-content: center;
- .beatSymbolImg {
- width: 44Px;
- height: 46Px;
- &.beatSymbolImg1 {
- background: url('./imgs/b1.png') no-repeat;
- background-size: 100% 100%;
- }
- &.beatSymbolImg2 {
- background: url('./imgs/b2.png') no-repeat;
- background-size: 100% 100%;
- }
- &.beatSymbolImg3 {
- background: url('./imgs/b3.png') no-repeat;
- background-size: 100% 100%;
- }
- &.beatSymbolImg4 {
- background: url('./imgs/b4.png') no-repeat;
- background-size: 100% 100%;
- }
- &.beatSymbolImg5 {
- background: url('./imgs/b5.png') no-repeat;
- background-size: 100% 100%;
- }
- &.beatSymbolImg6 {
- background: url('./imgs/b6.png') no-repeat;
- background-size: 100% 100%;
- }
- &.beatSymbolImg7 {
- background: url('./imgs/b7.png') no-repeat;
- background-size: 100% 100%;
- }
- }
- }
- }
- }
- .sliderList {
- margin-top: 24Px;
- display: flex;
- align-items: center;
- & > img {
- width: 18Px;
- height: 18Px;
- margin-right: 16Px;
- }
- .sliderText {
- width: 32Px;
- font-weight: 500;
- font-size: 16Px;
- color: #1cacf1;
- margin-left: 16Px;
- }
- :deep(.n-slider-rail) {
- height: 6Px;
- line-height: 6Px;
- background: #ffffff;
- border-radius: 4Px;
- width: 194Px;
- .n-slider-rail__fill {
- background: linear-gradient(90deg, #63daff 0%, #1798ff 100%);
- border-radius: 4Px;
- }
- }
- .thumbDot {
- width: 21Px;
- height: 21Px;
- }
- }
- .playBtn {
- cursor: pointer;
- margin-top: 20Px;
- width: 272Px;
- height: 54Px;
- background: #00acff;
- border-radius: 27Px;
- display: flex;
- justify-content: center;
- align-items: center;
- font-weight: 600;
- font-size: 18Px;
- color: #ffffff;
- & > img {
- margin-left: 14Px;
- width: 13Px;
- height: 14Px;
- &.pauseImg {
- width: 12Px;
- height: 13Px;
- }
- }
- }
- }
- </style>
- <style lang="less">
- .n-base-select-menu.n-select-menu {
- .beatValOptItem {
- padding: 0 9Px !important;
- display: flex;
- justify-content: center;
- &.n-base-select-option::before {
- transition: initial;
- }
- &.n-base-select-option--selected {
- .n-base-select-option__content {
- color: #47a7fe;
- }
- }
- &.n-base-select-option--pending {
- .n-base-select-option__content {
- color: #ffffff;
- }
- &::before {
- background: #47a7fe !important;
- border-radius: 6Px !important;
- left: 9Px;
- right: 9Px;
- }
- }
- .n-base-select-option__content {
- font-weight: 500;
- font-size: 18Px;
- color: #777777;
- }
- }
- .beatSymbolOptItem {
- display: flex;
- justify-content: center;
- &.n-base-select-option::before {
- transition: initial;
- }
- &.n-base-select-option--selected {
- .n-base-select-option__content {
- .beatSymbolImg {
- &.beatSymbolImg1 {
- background: url('./imgs/a1.png') no-repeat;
- background-size: 100% 100%;
- }
- &.beatSymbolImg2 {
- background: url('./imgs/a2.png') no-repeat;
- background-size: 100% 100%;
- }
- &.beatSymbolImg3 {
- background: url('./imgs/a3.png') no-repeat;
- background-size: 100% 100%;
- }
- &.beatSymbolImg4 {
- background: url('./imgs/a4.png') no-repeat;
- background-size: 100% 100%;
- }
- &.beatSymbolImg5 {
- background: url('./imgs/a5.png') no-repeat;
- background-size: 100% 100%;
- }
- &.beatSymbolImg6 {
- background: url('./imgs/a6.png') no-repeat;
- background-size: 100% 100%;
- }
- &.beatSymbolImg7 {
- background: url('./imgs/a7.png') no-repeat;
- background-size: 100% 100%;
- }
- }
- }
- }
- &.n-base-select-option--pending {
- .n-base-select-option__content {
- .beatSymbolImg {
- &.beatSymbolImg1 {
- background: url('./imgs/c1.png') no-repeat;
- background-size: 100% 100%;
- }
- &.beatSymbolImg2 {
- background: url('./imgs/c2.png') no-repeat;
- background-size: 100% 100%;
- }
- &.beatSymbolImg3 {
- background: url('./imgs/c3.png') no-repeat;
- background-size: 100% 100%;
- }
- &.beatSymbolImg4 {
- background: url('./imgs/c4.png') no-repeat;
- background-size: 100% 100%;
- }
- &.beatSymbolImg5 {
- background: url('./imgs/c5.png') no-repeat;
- background-size: 100% 100%;
- }
- &.beatSymbolImg6 {
- background: url('./imgs/c6.png') no-repeat;
- background-size: 100% 100%;
- }
- &.beatSymbolImg7 {
- background: url('./imgs/c7.png') no-repeat;
- background-size: 100% 100%;
- }
- }
- }
- &::before {
- background: #47a7fe !important;
- border-radius: 6Px !important;
- left: 9Px;
- right: 9Px;
- }
- }
- .beatSymbolImg {
- width: 44Px;
- height: 46Px;
- &.beatSymbolImg1 {
- background: url('./imgs/b1.png') no-repeat;
- background-size: 100% 100%;
- }
- &.beatSymbolImg2 {
- background: url('./imgs/b2.png') no-repeat;
- background-size: 100% 100%;
- }
- &.beatSymbolImg3 {
- background: url('./imgs/b3.png') no-repeat;
- background-size: 100% 100%;
- }
- &.beatSymbolImg4 {
- background: url('./imgs/b4.png') no-repeat;
- background-size: 100% 100%;
- }
- &.beatSymbolImg5 {
- background: url('./imgs/b5.png') no-repeat;
- background-size: 100% 100%;
- }
- &.beatSymbolImg6 {
- background: url('./imgs/b6.png') no-repeat;
- background-size: 100% 100%;
- }
- &.beatSymbolImg7 {
- background: url('./imgs/b7.png') no-repeat;
- background-size: 100% 100%;
- }
- }
- }
- }
- .metronomeConBoxClass_drag
- .v-binder-follower-container
- .n-base-select-menu.n-select-menu {
- --n-border-radius: 10Px !important;
- .n-scrollbar-rail.n-scrollbar-rail--vertical {
- right: 3Px;
- --n-scrollbar-width: 3Px !important;
- opacity: 0.4;
- }
- }
- </style>
|