audioSlider.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. import React from 'react';
  2. import cls from 'classnames';
  3. import '@douyinfe/semi-foundation/audioPlayer/audioPlayer.scss';
  4. import { cssClasses } from '@douyinfe/semi-foundation/audioPlayer/constants';
  5. import Tooltip from '../tooltip';
  6. import { formatTime } from './utils';
  7. import { noop } from 'lodash';
  8. import { AudioPlayerTheme } from './index';
  9. interface AudioSliderProps {
  10. value: number;
  11. onChange?: (value: number) => void;
  12. className?: string;
  13. max?: number;
  14. vertical?: boolean;
  15. width?: number;
  16. height?: number;
  17. showTooltip?: boolean;
  18. disabled?: boolean;
  19. theme?: AudioPlayerTheme
  20. }
  21. interface AudioSliderState {
  22. isDragging: boolean;
  23. movingInfo: { progress: number; offset: number } | null;
  24. isHovering: boolean
  25. }
  26. const prefixCls = cssClasses.PREFIX;
  27. export default class AudioSlider extends React.Component<AudioSliderProps, AudioSliderState> {
  28. static defaultProps = {
  29. value: 0,
  30. onChange: noop,
  31. max: 100,
  32. vertical: false,
  33. width: '100%',
  34. height: 4,
  35. showTooltip: true,
  36. disabled: false,
  37. theme: 'dark'
  38. };
  39. private sliderRef: React.RefObject<HTMLDivElement>;
  40. private handleRef: React.RefObject<HTMLDivElement>;
  41. constructor(props: AudioSliderProps) {
  42. super(props);
  43. this.state = {
  44. isDragging: false,
  45. isHovering: false,
  46. movingInfo: null,
  47. };
  48. this.sliderRef = React.createRef();
  49. this.handleRef = React.createRef();
  50. }
  51. handleMouseEnter = (e: React.MouseEvent) => {
  52. this.setState({ isHovering: true });
  53. this.handleMouseEvent(e, false);
  54. }
  55. handleMouseDown = (e: React.MouseEvent) => {
  56. this.setState({ isDragging: true });
  57. this.handleMouseEvent(e, true);
  58. };
  59. handleMouseUp = () => {
  60. if (this.state.isDragging) {
  61. this.setState({ isDragging: false });
  62. }
  63. };
  64. handleMouseEvent = (e: React.MouseEvent, shouldSetValue: boolean = true) => {
  65. if (!this.sliderRef.current || this.props.disabled) return;
  66. const rect = this.sliderRef.current.getBoundingClientRect();
  67. const offset = this.props.vertical ?
  68. (rect.bottom - e.clientY) :
  69. (e.clientX - rect.left);
  70. const total = this.props.vertical ? rect.height : rect.width;
  71. const percentage = Math.min(Math.max(offset / total, 0), 1);
  72. const value = percentage * this.props.max;
  73. if (shouldSetValue && (this.state.isDragging || e.type === 'mousedown')) {
  74. this.props.onChange(value);
  75. }
  76. this.setState({
  77. movingInfo: {
  78. progress: percentage,
  79. offset: this.props.vertical ? offset - rect.height / 2 : offset - rect.width / 2
  80. },
  81. });
  82. };
  83. handleMouseMove = (e: React.MouseEvent) => {
  84. this.handleMouseEvent(e, true);
  85. }
  86. handleMouseLeave = () => {
  87. this.setState({
  88. isHovering: false,
  89. isDragging: false
  90. });
  91. }
  92. render() {
  93. const { vertical, width, height, showTooltip, max, value: currentValue, theme } = this.props;
  94. const { movingInfo, isHovering } = this.state;
  95. const sliderContent = (
  96. <div
  97. onMouseDown={this.handleMouseDown}
  98. onMouseUp={this.handleMouseUp}
  99. onMouseEnter={this.handleMouseEnter}
  100. onMouseLeave={this.handleMouseLeave}
  101. onMouseMove={this.handleMouseMove}
  102. className={cls(`${prefixCls}-slider-wrapper`, {
  103. [`${prefixCls}-slider-wrapper-vertical`]: vertical,
  104. [`${prefixCls}-slider-wrapper-horizontal`]: !vertical,
  105. })}
  106. >
  107. <div
  108. ref={this.sliderRef}
  109. className={cls(`${prefixCls}-slider`, `${prefixCls}-slider-${theme}`, {
  110. [`${prefixCls}-slider-vertical`]: vertical,
  111. [`${prefixCls}-slider-horizontal`]: !vertical,
  112. })}
  113. style={{
  114. width: vertical ? (isHovering ? 8 : 4) : width,
  115. height: vertical ? height : (isHovering ? 8 : 4),
  116. }}
  117. >
  118. <div
  119. className={cls(`${prefixCls}-slider-progress`, {
  120. [`${prefixCls}-slider-progress-vertical`]: vertical,
  121. [`${prefixCls}-slider-progress-horizontal`]: !vertical,
  122. })}
  123. style={{
  124. height: vertical ? `${(currentValue / max) * 100}%` : '100%',
  125. width: vertical ? '100%' : `${(currentValue / max) * 100}%`,
  126. }}
  127. />
  128. <div
  129. ref={this.handleRef}
  130. className={cls(`${prefixCls}-slider-dot`)}
  131. style={{
  132. left: vertical ? '50%' : `calc(${(currentValue / max) * 100}% - 8px)`,
  133. bottom: vertical ? `calc(${(currentValue / max) * 100}% - 8px)` : undefined,
  134. top: vertical ? undefined : '50%',
  135. transform: vertical ? 'translateX(-50%)' : 'translateY(-50%)',
  136. opacity: isHovering ? 1 : 0,
  137. transition: 'opacity 0.2s',
  138. pointerEvents: 'none',
  139. }}
  140. />
  141. </div>
  142. </div>
  143. );
  144. return showTooltip ? (
  145. <Tooltip
  146. position={vertical ? 'right' : 'top'}
  147. autoAdjustOverflow
  148. content={formatTime(movingInfo?.progress * max)}
  149. style={{
  150. [vertical ? 'top' : 'left']: movingInfo?.offset,
  151. }}
  152. >
  153. {sliderContent}
  154. </Tooltip>
  155. ) : sliderContent;
  156. }
  157. }