Values.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. import { EventDispatcher } from 'three';
  2. class Value extends EventDispatcher {
  3. constructor() {
  4. super();
  5. this.domElement = document.createElement( 'div' );
  6. this.domElement.className = 'param-control';
  7. this._onChangeFunction = null;
  8. this.addEventListener( 'change', ( e ) => {
  9. // defer to avoid issues when changing multiple values in the same call stack
  10. requestAnimationFrame( () => {
  11. if ( this._onChangeFunction ) this._onChangeFunction( e.value );
  12. } );
  13. } );
  14. }
  15. getValue() {
  16. return null;
  17. }
  18. dispatchChange() {
  19. this.dispatchEvent( { type: 'change', value: this.getValue() } );
  20. }
  21. onChange( callback ) {
  22. this._onChangeFunction = callback;
  23. return this;
  24. }
  25. }
  26. class ValueNumber extends Value {
  27. constructor( { value = 0, step = 0.1, min = - Infinity, max = Infinity } ) {
  28. super();
  29. this.input = document.createElement( 'input' );
  30. this.input.type = 'number';
  31. this.input.value = value;
  32. this.input.step = step;
  33. this.input.min = min;
  34. this.input.max = max;
  35. this.input.addEventListener( 'change', this._onChangeValue.bind( this ) );
  36. this.domElement.appendChild( this.input );
  37. this.addDragHandler();
  38. }
  39. _onChangeValue() {
  40. const value = parseFloat( this.input.value );
  41. const min = parseFloat( this.input.min );
  42. const max = parseFloat( this.input.max );
  43. if ( value > max ) {
  44. this.input.value = max;
  45. } else if ( value < min ) {
  46. this.input.value = min;
  47. } else if ( isNaN( value ) ) {
  48. this.input.value = min;
  49. }
  50. this.dispatchChange();
  51. }
  52. step( value ) {
  53. this.input.step = value;
  54. return this;
  55. }
  56. addDragHandler() {
  57. let isDragging = false;
  58. let startY, startValue;
  59. this.input.addEventListener( 'mousedown', ( e ) => {
  60. isDragging = true;
  61. startY = e.clientY;
  62. startValue = parseFloat( this.input.value );
  63. document.body.style.cursor = 'ns-resize';
  64. } );
  65. document.addEventListener( 'mousemove', ( e ) => {
  66. if ( isDragging ) {
  67. const deltaY = startY - e.clientY;
  68. const step = parseFloat( this.input.step ) || 1;
  69. const min = parseFloat( this.input.min );
  70. const max = parseFloat( this.input.max );
  71. let stepSize = step;
  72. if ( ! isNaN( max ) && isFinite( min ) ) {
  73. stepSize = ( max - min ) / 100;
  74. }
  75. const change = deltaY * stepSize;
  76. let newValue = startValue + change;
  77. newValue = Math.max( min, Math.min( newValue, max ) );
  78. const precision = ( String( step ).split( '.' )[ 1 ] || [] ).length;
  79. this.input.value = newValue.toFixed( precision );
  80. this.input.dispatchEvent( new Event( 'input' ) );
  81. this.dispatchChange();
  82. }
  83. } );
  84. document.addEventListener( 'mouseup', () => {
  85. if ( isDragging ) {
  86. isDragging = false;
  87. document.body.style.cursor = 'default';
  88. }
  89. } );
  90. }
  91. getValue() {
  92. return parseFloat( this.input.value );
  93. }
  94. }
  95. class ValueCheckbox extends Value {
  96. constructor( { value = false } ) {
  97. super();
  98. const label = document.createElement( 'label' );
  99. label.className = 'custom-checkbox';
  100. const checkbox = document.createElement( 'input' );
  101. checkbox.type = 'checkbox';
  102. checkbox.checked = value;
  103. this.checkbox = checkbox;
  104. const checkmark = document.createElement( 'span' );
  105. checkmark.className = 'checkmark';
  106. label.appendChild( checkbox );
  107. label.appendChild( checkmark );
  108. this.domElement.appendChild( label );
  109. checkbox.addEventListener( 'change', () => {
  110. this.dispatchChange();
  111. } );
  112. }
  113. getValue() {
  114. return this.checkbox.checked;
  115. }
  116. }
  117. class ValueSlider extends Value {
  118. constructor( { value = 0, min = 0, max = 1, step = 0.01 } ) {
  119. super();
  120. this.slider = document.createElement( 'input' );
  121. this.slider.type = 'range';
  122. this.slider.min = min;
  123. this.slider.max = max;
  124. this.slider.step = step;
  125. const numberValue = new ValueNumber( { value, min, max, step } );
  126. this.numberInput = numberValue.input;
  127. this.numberInput.style.width = '60px';
  128. this.numberInput.style.flexShrink = '0';
  129. this.slider.value = value;
  130. this.domElement.append( this.slider, this.numberInput );
  131. this.slider.addEventListener( 'input', () => {
  132. this.numberInput.value = this.slider.value;
  133. this.dispatchChange();
  134. } );
  135. numberValue.addEventListener( 'change', () => {
  136. this.slider.value = parseFloat( this.numberInput.value );
  137. this.dispatchChange();
  138. } );
  139. }
  140. getValue() {
  141. return parseFloat( this.slider.value );
  142. }
  143. step( value ) {
  144. this.slider.step = value;
  145. this.numberInput.step = value;
  146. return this;
  147. }
  148. }
  149. class ValueSelect extends Value {
  150. constructor( { options = [], value = '' } ) {
  151. super();
  152. const select = document.createElement( 'select' );
  153. const createOption = ( name, optionValue ) => {
  154. const optionEl = document.createElement( 'option' );
  155. optionEl.value = name;
  156. optionEl.textContent = name;
  157. if ( optionValue == value ) optionEl.selected = true;
  158. select.appendChild( optionEl );
  159. return optionEl;
  160. };
  161. if ( Array.isArray( options ) ) {
  162. options.forEach( opt => createOption( opt, opt ) );
  163. } else {
  164. Object.entries( options ).forEach( ( [ key, value ] ) => createOption( key, value ) );
  165. }
  166. this.domElement.appendChild( select );
  167. //
  168. select.addEventListener( 'change', () => {
  169. this.dispatchChange();
  170. } );
  171. this.options = options;
  172. this.select = select;
  173. }
  174. getValue() {
  175. const options = this.options;
  176. if ( Array.isArray( options ) ) {
  177. return options[ this.select.selectedIndex ];
  178. } else {
  179. return options[ this.select.value ];
  180. }
  181. }
  182. }
  183. class ValueColor extends Value {
  184. constructor( { value = '#ffffff' } ) {
  185. super();
  186. const colorInput = document.createElement( 'input' );
  187. colorInput.type = 'color';
  188. colorInput.value = this._getColorHex( value );
  189. this.colorInput = colorInput;
  190. this._value = value;
  191. colorInput.addEventListener( 'input', () => {
  192. const colorValue = colorInput.value;
  193. if ( this._value.isColor ) {
  194. this._value.setHex( parseInt( colorValue.slice( 1 ), 16 ) );
  195. } else {
  196. this._value = colorValue;
  197. }
  198. this.dispatchChange();
  199. } );
  200. this.domElement.appendChild( colorInput );
  201. }
  202. _getColorHex( color ) {
  203. if ( color.isColor ) {
  204. color = color.getHex();
  205. }
  206. if ( typeof color === 'number' ) {
  207. color = `#${ color.toString( 16 ) }`;
  208. }
  209. return color;
  210. }
  211. getValue() {
  212. let value = this._value;
  213. if ( typeof value === 'string' ) {
  214. value = parseInt( value.slice( 1 ), 16 );
  215. }
  216. return value;
  217. }
  218. }
  219. export { Value, ValueNumber, ValueCheckbox, ValueSlider, ValueSelect, ValueColor };
粤ICP备19079148号