Values.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  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. setValue( /*val*/ ) {
  16. this.dispatchChange();
  17. return this;
  18. }
  19. getValue() {
  20. return null;
  21. }
  22. dispatchChange() {
  23. this.dispatchEvent( { type: 'change', value: this.getValue() } );
  24. }
  25. onChange( callback ) {
  26. this._onChangeFunction = callback;
  27. return this;
  28. }
  29. }
  30. class ValueNumber extends Value {
  31. constructor( { value = 0, step = 0.1, min = - Infinity, max = Infinity } ) {
  32. super();
  33. this.input = document.createElement( 'input' );
  34. this.input.type = 'number';
  35. this.input.value = value;
  36. this.input.step = step;
  37. this.input.min = min;
  38. this.input.max = max;
  39. this.input.addEventListener( 'change', this._onChangeValue.bind( this ) );
  40. this.domElement.appendChild( this.input );
  41. this.addDragHandler();
  42. }
  43. _onChangeValue() {
  44. const value = parseFloat( this.input.value );
  45. const min = parseFloat( this.input.min );
  46. const max = parseFloat( this.input.max );
  47. if ( value > max ) {
  48. this.input.value = max;
  49. } else if ( value < min ) {
  50. this.input.value = min;
  51. } else if ( isNaN( value ) ) {
  52. this.input.value = min;
  53. }
  54. this.dispatchChange();
  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. setValue( val ) {
  92. this.input.value = val;
  93. return super.setValue( val );
  94. }
  95. getValue() {
  96. return parseFloat( this.input.value );
  97. }
  98. }
  99. class ValueCheckbox extends Value {
  100. constructor( { value = false } ) {
  101. super();
  102. const label = document.createElement( 'label' );
  103. label.className = 'custom-checkbox';
  104. const checkbox = document.createElement( 'input' );
  105. checkbox.type = 'checkbox';
  106. checkbox.checked = value;
  107. this.checkbox = checkbox;
  108. const checkmark = document.createElement( 'span' );
  109. checkmark.className = 'checkmark';
  110. label.appendChild( checkbox );
  111. label.appendChild( checkmark );
  112. this.domElement.appendChild( label );
  113. checkbox.addEventListener( 'change', () => {
  114. this.dispatchChange();
  115. } );
  116. }
  117. setValue( val ) {
  118. this.checkbox.value = val;
  119. return super.setValue( val );
  120. }
  121. getValue() {
  122. return this.checkbox.checked;
  123. }
  124. }
  125. class ValueSlider extends Value {
  126. constructor( { value = 0, min = 0, max = 1, step = 0.01 } ) {
  127. super();
  128. this.slider = document.createElement( 'input' );
  129. this.slider.type = 'range';
  130. this.slider.min = min;
  131. this.slider.max = max;
  132. this.slider.step = step;
  133. const numberValue = new ValueNumber( { value, min, max, step } );
  134. this.numberInput = numberValue.input;
  135. this.numberInput.style.flexBasis = '80px';
  136. this.numberInput.style.flexShrink = '0';
  137. this.slider.value = value;
  138. this.domElement.append( this.slider, this.numberInput );
  139. this.slider.addEventListener( 'input', () => {
  140. this.numberInput.value = this.slider.value;
  141. this.dispatchChange();
  142. } );
  143. numberValue.addEventListener( 'change', () => {
  144. this.slider.value = parseFloat( this.numberInput.value );
  145. this.dispatchChange();
  146. } );
  147. }
  148. setValue( val ) {
  149. this.slider.value = val;
  150. this.numberInput.value = val;
  151. return super.setValue( val );
  152. }
  153. getValue() {
  154. return parseFloat( this.slider.value );
  155. }
  156. step( value ) {
  157. this.slider.step = value;
  158. this.numberInput.step = value;
  159. return this;
  160. }
  161. }
  162. class ValueSelect extends Value {
  163. constructor( { options = [], value = '' } ) {
  164. super();
  165. const select = document.createElement( 'select' );
  166. const createOption = ( name, optionValue ) => {
  167. const optionEl = document.createElement( 'option' );
  168. optionEl.value = name;
  169. optionEl.textContent = name;
  170. if ( optionValue == value ) optionEl.selected = true;
  171. select.appendChild( optionEl );
  172. return optionEl;
  173. };
  174. if ( Array.isArray( options ) ) {
  175. options.forEach( opt => createOption( opt, opt ) );
  176. } else {
  177. Object.entries( options ).forEach( ( [ key, value ] ) => createOption( key, value ) );
  178. }
  179. this.domElement.appendChild( select );
  180. //
  181. select.addEventListener( 'change', () => {
  182. this.dispatchChange();
  183. } );
  184. this.options = options;
  185. this.select = select;
  186. }
  187. getValue() {
  188. const options = this.options;
  189. if ( Array.isArray( options ) ) {
  190. return options[ this.select.selectedIndex ];
  191. } else {
  192. return options[ this.select.value ];
  193. }
  194. }
  195. }
  196. class ValueColor extends Value {
  197. constructor( { value = '#ffffff' } ) {
  198. super();
  199. const colorInput = document.createElement( 'input' );
  200. colorInput.type = 'color';
  201. colorInput.value = this._getColorHex( value );
  202. this.colorInput = colorInput;
  203. this._value = value;
  204. colorInput.addEventListener( 'input', () => {
  205. const colorValue = colorInput.value;
  206. if ( this._value.isColor ) {
  207. this._value.setHex( parseInt( colorValue.slice( 1 ), 16 ) );
  208. } else {
  209. this._value = colorValue;
  210. }
  211. this.dispatchChange();
  212. } );
  213. this.domElement.appendChild( colorInput );
  214. }
  215. _getColorHex( color ) {
  216. if ( color.isColor ) {
  217. color = color.getHex();
  218. }
  219. if ( typeof color === 'number' ) {
  220. color = `#${ color.toString( 16 ) }`;
  221. } else if ( color[ 0 ] !== '#' ) {
  222. color = '#' + color;
  223. }
  224. return color;
  225. }
  226. getValue() {
  227. let value = this._value;
  228. if ( typeof value === 'string' ) {
  229. value = parseInt( value.slice( 1 ), 16 );
  230. }
  231. return value;
  232. }
  233. }
  234. class ValueButton extends Value {
  235. constructor( { text = 'Button', value = () => {} } ) {
  236. super();
  237. const button = document.createElement( 'button' );
  238. button.textContent = text;
  239. button.onclick = value;
  240. this.domElement.appendChild( button );
  241. }
  242. }
  243. export { Value, ValueNumber, ValueCheckbox, ValueSlider, ValueSelect, ValueColor, ValueButton };
粤ICP备19079148号