Chcę utworzyć komponent React z możliwością przeciągania (czyli repozycjonowania za pomocą myszy), który wydaje się koniecznie obejmować obsługę stanu globalnego i rozproszonych zdarzeń. Mogę to zrobić na brudno, z globalną zmienną w moim pliku JS i prawdopodobnie mógłbym nawet owinąć ją ładnym interfejsem zamknięcia, ale chcę wiedzieć, czy istnieje sposób, który lepiej współgra z React.
Ponadto, ponieważ nigdy wcześniej nie robiłem tego w surowym języku JavaScript, chciałbym zobaczyć, jak robi to ekspert, aby upewnić się, że załatwiłem wszystkie przypadki narożne, zwłaszcza te związane z Reactem.
Dzięki.
javascript
reactjs
Andrew Fleenor
źródło
źródło
Odpowiedzi:
Powinienem prawdopodobnie zamienić to w post na blogu, ale oto całkiem solidny przykład.
Komentarze powinny dość dobrze wyjaśniać, ale daj mi znać, jeśli masz pytania.
A oto skrzypce do zabawy: http://jsfiddle.net/Af9Jt/2/
var Draggable = React.createClass({ getDefaultProps: function () { return { // allow the initial position to be passed in as a prop initialPos: {x: 0, y: 0} } }, getInitialState: function () { return { pos: this.props.initialPos, dragging: false, rel: null // position relative to the cursor } }, // we could get away with not having this (and just having the listeners on // our div), but then the experience would be possibly be janky. If there's // anything w/ a higher z-index that gets in the way, then you're toast, // etc. componentDidUpdate: function (props, state) { if (this.state.dragging && !state.dragging) { document.addEventListener('mousemove', this.onMouseMove) document.addEventListener('mouseup', this.onMouseUp) } else if (!this.state.dragging && state.dragging) { document.removeEventListener('mousemove', this.onMouseMove) document.removeEventListener('mouseup', this.onMouseUp) } }, // calculate relative position to the mouse and set dragging=true onMouseDown: function (e) { // only left mouse button if (e.button !== 0) return var pos = $(this.getDOMNode()).offset() this.setState({ dragging: true, rel: { x: e.pageX - pos.left, y: e.pageY - pos.top } }) e.stopPropagation() e.preventDefault() }, onMouseUp: function (e) { this.setState({dragging: false}) e.stopPropagation() e.preventDefault() }, onMouseMove: function (e) { if (!this.state.dragging) return this.setState({ pos: { x: e.pageX - this.state.rel.x, y: e.pageY - this.state.rel.y } }) e.stopPropagation() e.preventDefault() }, render: function () { // transferPropsTo will merge style & other props passed into our // component to also be on the child DIV. return this.transferPropsTo(React.DOM.div({ onMouseDown: this.onMouseDown, style: { left: this.state.pos.x + 'px', top: this.state.pos.y + 'px' } }, this.props.children)) } })
Myśli o własności państwowej itp.
„Kto powinien posiadać jaki stan” to ważne pytanie od samego początku. W przypadku komponentu „przeciągalnego” mogłem zobaczyć kilka różnych scenariuszy.
Scenariusz 1
rodzic powinien być właścicielem bieżącej pozycji elementu przeciąganego. W tym przypadku element do przeciągania nadal będzie posiadał stan „czy przeciągam”, ale będzie wywoływał,
this.props.onChange(x, y)
gdy wystąpi zdarzenie przesunięcia myszy.Scenariusz 2
rodzic musi posiadać tylko „nieruchomą pozycję”, więc element do przeciągania będzie posiadał swoją „pozycję przeciągania”, ale po włączeniu myszy wywoła
this.props.onChange(x, y)
i odroczy ostateczną decyzję do rodzica. Jeśli rodzicowi nie podoba się miejsce, w którym zakończył się element przeciągany, po prostu nie zaktualizowałby swojego stanu, a element do przeciągania „wskoczyłby z powrotem” do swojej początkowej pozycji przed przeciągnięciem.Mieszanka czy składnik?
@ssorallen zwrócił uwagę, że skoro „draggable” jest bardziej atrybutem niż rzeczą samą w sobie, może lepiej służyć jako mieszanka. Moje doświadczenie z mieszankami jest ograniczone, więc nie widziałem, jak mogą pomóc lub przeszkodzić w skomplikowanych sytuacjach. To może być najlepsza opcja.
źródło
Mixin
niż pełna klasa, ponieważ „Draggable” w rzeczywistości nie jest obiektem, jest to zdolność obiektu.var computedStyle = window.getComputedStyle(this.getDOMNode()); pos = { top: parseInt(computedStyle.top), left: parseInt(computedStyle.left) };
Jeśli używasz jquery z reakcją, prawdopodobnie robisz coś źle;) Jeśli potrzebujesz jakiejś wtyczki jquery, stwierdzam, że zwykle łatwiej i mniej kodu jest przepisanie go w czystej reakcji.this.getDOMNode().getBoundingClientRect()
- getComputedStyle może wyprowadzić dowolną prawidłową właściwość CSS,auto
w tym w takim przypadku powyższy kod spowoduje utworzenie plikuNaN
. Zobacz artykuł w MDN: developer.mozilla.org/en-US/docs/Web/API/Element/ ...this.getDOMNode()
, to zostało wycofane. Użyj ref, aby uzyskać węzeł dom. facebook.github.io/react/docs/…Zaimplementowałem react-dnd , elastyczną mieszankę HTML5 typu przeciągnij i upuść dla Reacta z pełną kontrolą DOM.
Istniejące biblioteki typu „przeciągnij i upuść” nie pasowały do mojego przypadku użycia, więc napisałem własną. Jest podobny do kodu, który używamy od około roku na Stampsy.com, ale został przepisany, aby wykorzystać React i Flux.
Kluczowe wymagania jakie miałem:
Jeśli to brzmi znajomo, czytaj dalej.
Stosowanie
Proste źródło przeciągania
Najpierw zadeklaruj typy danych, które można przeciągać.
Służą one do sprawdzania „zgodności” źródeł przeciągania i celów upuszczania:
// ItemTypes.js module.exports = { BLOCK: 'block', IMAGE: 'image' };
(Jeśli nie masz wielu typów danych, ta biblioteka może nie być dla Ciebie).
Następnie stwórzmy bardzo prosty komponent do przeciągania, który po przeciągnięciu reprezentuje
IMAGE
:var { DragDropMixin } = require('react-dnd'), ItemTypes = require('./ItemTypes'); var Image = React.createClass({ mixins: [DragDropMixin], configureDragDrop(registerType) { // Specify all supported types by calling registerType(type, { dragSource?, dropTarget? }) registerType(ItemTypes.IMAGE, { // dragSource, when specified, is { beginDrag(), canDrag()?, endDrag(didDrop)? } dragSource: { // beginDrag should return { item, dragOrigin?, dragPreview?, dragEffect? } beginDrag() { return { item: this.props.image }; } } }); }, render() { // {...this.dragSourceFor(ItemTypes.IMAGE)} will expand into // { draggable: true, onDragStart: (handled by mixin), onDragEnd: (handled by mixin) }. return ( <img src={this.props.image.url} {...this.dragSourceFor(ItemTypes.IMAGE)} /> ); } );
Podając specyfikację
configureDragDrop
, mówimy oDragDropMixin
zachowaniu tego komponentu przez przeciąganie i upuszczanie. Komponenty, które można przeciągać i upuszczać, używają tego samego miksu.Wewnątrz
configureDragDrop
musimy zadzwonićregisterType
do każdego z naszych niestandardowychItemTypes
obsługiwanych przez ten składnik. Na przykład, nie może być kilka reprezentacje obrazów w aplikacji, a każda będzie dostarczeniedragSource
doItemTypes.IMAGE
.A
dragSource
to po prostu obiekt określający sposób działania źródła przeciągania. Musisz zaimplementować,beginDrag
aby zwrócić element, który reprezentuje przeciągane dane i opcjonalnie kilka opcji, które dostosowują przeciągany interfejs użytkownika. Opcjonalnie możesz zaimplementować,canDrag
aby zabronić przeciągania lubendDrag(didDrop)
wykonać jakąś logikę, gdy upuszczenie wystąpiło (lub nie). Możesz dzielić tę logikę między komponentami, pozwalając na generowaniedragSource
dla nich współdzielonego miksu .Na koniec musisz użyć
{...this.dragSourceFor(itemType)}
na niektórych (jednym lub więcej) elementach programu,render
aby dołączyć uchwyty przeciągania. Oznacza to, że możesz mieć kilka „uchwytów przeciągania” w jednym elemencie i mogą one nawet odpowiadać różnym typom elementów. (Jeśli nie znasz składni JSX Spread Attributes , sprawdź to).Prosty cel upuszczenia
Powiedzmy, że chcemy
ImageBlock
być celem upuszczenia dlaIMAGE
s. To niemal tak samo, z tym że musimy daćregisterType
dodropTarget
realizacji:var { DragDropMixin } = require('react-dnd'), ItemTypes = require('./ItemTypes'); var ImageBlock = React.createClass({ mixins: [DragDropMixin], configureDragDrop(registerType) { registerType(ItemTypes.IMAGE, { // dropTarget, when specified, is { acceptDrop(item)?, enter(item)?, over(item)?, leave(item)? } dropTarget: { acceptDrop(image) { // Do something with image! for example, DocumentActionCreators.setImage(this.props.blockId, image); } } }); }, render() { // {...this.dropTargetFor(ItemTypes.IMAGE)} will expand into // { onDragEnter: (handled by mixin), onDragOver: (handled by mixin), onDragLeave: (handled by mixin), onDrop: (handled by mixin) }. return ( <div {...this.dropTargetFor(ItemTypes.IMAGE)}> {this.props.image && <img src={this.props.image.url} /> } </div> ); } );
Przeciągnij źródło + upuść cel w jednym komponencie
Powiedzmy, że teraz chcemy, aby użytkownik mógł wyciągnąć obraz z
ImageBlock
. Musimy tylko dodaćdragSource
do niego odpowiednie i kilka handlerów:var { DragDropMixin } = require('react-dnd'), ItemTypes = require('./ItemTypes'); var ImageBlock = React.createClass({ mixins: [DragDropMixin], configureDragDrop(registerType) { registerType(ItemTypes.IMAGE, { // Add a drag source that only works when ImageBlock has an image: dragSource: { canDrag() { return !!this.props.image; }, beginDrag() { return { item: this.props.image }; } } dropTarget: { acceptDrop(image) { DocumentActionCreators.setImage(this.props.blockId, image); } } }); }, render() { return ( <div {...this.dropTargetFor(ItemTypes.IMAGE)}> {/* Add {...this.dragSourceFor} handlers to a nested node */} {this.props.image && <img src={this.props.image.url} {...this.dragSourceFor(ItemTypes.IMAGE)} /> } </div> ); } );
Co jeszcze jest możliwe?
Nie opisałem wszystkiego, ale można użyć tego API na kilka innych sposobów:
getDragState(type)
i,getDropState(type)
aby dowiedzieć się, czy przeciąganie jest aktywne, i użyj go do przełączania klas lub atrybutów CSS;dragPreview
abyImage
używać obrazów jako symboli zastępczych przeciągania (użyj,ImagePreloaderMixin
aby je załadować);ImageBlocks
zamówić. Potrzebujemy ich tylko do wdrożeniadropTarget
idragSource
dlaItemTypes.BLOCK
.dropTargetFor(...types)
pozwala określić kilka typów na raz, więc jedna strefa zrzutu może złapać wiele różnych typów.Aby uzyskać aktualną dokumentację i instrukcje instalacji, przejdź na stronę react-dnd repo na Github .
źródło
Odpowiedź Jareda Forsytha jest strasznie błędna i nieaktualna. Podąża za całym zestawem antywzorów, takich jak użycie
stopPropagation
, inicjalizacja stanu z właściwości, użycie jQuery, zagnieżdżone obiekty w stanie i ma dziwnedragging
pole stanu. Jeśli zostanie przepisane, rozwiązanie będzie następujące, ale nadal wymusza uzgadnianie wirtualnego DOM przy każdym ruchu myszy i nie jest zbyt wydajne.UPD. Moja odpowiedź była strasznie błędna i nieaktualna. Teraz kod łagodzi problemy związane z powolnym cyklem życia komponentu React, używając natywnych procedur obsługi zdarzeń i aktualizacji stylów, używa,
transform
ponieważ nie prowadzi do ponownego przepływu i ogranicza zmiany DOMrequestAnimationFrame
. Teraz to dla mnie konsekwentnie 60 FPS w każdej przeglądarce, którą wypróbowałem.const throttle = (f) => { let token = null, lastArgs = null; const invoke = () => { f(...lastArgs); token = null; }; const result = (...args) => { lastArgs = args; if (!token) { token = requestAnimationFrame(invoke); } }; result.cancel = () => token && cancelAnimationFrame(token); return result; }; class Draggable extends React.PureComponent { _relX = 0; _relY = 0; _ref = React.createRef(); _onMouseDown = (event) => { if (event.button !== 0) { return; } const {scrollLeft, scrollTop, clientLeft, clientTop} = document.body; // Try to avoid calling `getBoundingClientRect` if you know the size // of the moving element from the beginning. It forces reflow and is // the laggiest part of the code right now. Luckily it's called only // once per click. const {left, top} = this._ref.current.getBoundingClientRect(); this._relX = event.pageX - (left + scrollLeft - clientLeft); this._relY = event.pageY - (top + scrollTop - clientTop); document.addEventListener('mousemove', this._onMouseMove); document.addEventListener('mouseup', this._onMouseUp); event.preventDefault(); }; _onMouseUp = (event) => { document.removeEventListener('mousemove', this._onMouseMove); document.removeEventListener('mouseup', this._onMouseUp); event.preventDefault(); }; _onMouseMove = (event) => { this.props.onMove( event.pageX - this._relX, event.pageY - this._relY, ); event.preventDefault(); }; _update = throttle(() => { const {x, y} = this.props; this._ref.current.style.transform = `translate(${x}px, ${y}px)`; }); componentDidMount() { this._ref.current.addEventListener('mousedown', this._onMouseDown); this._update(); } componentDidUpdate() { this._update(); } componentWillUnmount() { this._ref.current.removeEventListener('mousedown', this._onMouseDown); this._update.cancel(); } render() { return ( <div className="draggable" ref={this._ref}> {this.props.children} </div> ); } } class Test extends React.PureComponent { state = { x: 100, y: 200, }; _move = (x, y) => this.setState({x, y}); // you can implement grid snapping logic or whatever here /* _move = (x, y) => this.setState({ x: ~~((x - 5) / 10) * 10 + 5, y: ~~((y - 5) / 10) * 10 + 5, }); */ render() { const {x, y} = this.state; return ( <Draggable x={x} y={y} onMove={this._move}> Drag me </Draggable> ); } } ReactDOM.render( <Test />, document.getElementById('container'), );
i trochę CSS
.draggable { /* just to size it to content */ display: inline-block; /* opaque background is important for performance */ background: white; /* avoid selecting text while dragging */ user-select: none; }
Przykład na JSFiddle.
źródło
Zaktualizowałem rozwiązanie polkovnikov.ph do React 16 / ES6 z ulepszeniami, takimi jak obsługa dotykowa i przyciąganie do siatki, czego potrzebuję w grze. Przyciąganie do siatki łagodzi problemy z wydajnością.
import React from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; class Draggable extends React.Component { constructor(props) { super(props); this.state = { relX: 0, relY: 0, x: props.x, y: props.y }; this.gridX = props.gridX || 1; this.gridY = props.gridY || 1; this.onMouseDown = this.onMouseDown.bind(this); this.onMouseMove = this.onMouseMove.bind(this); this.onMouseUp = this.onMouseUp.bind(this); this.onTouchStart = this.onTouchStart.bind(this); this.onTouchMove = this.onTouchMove.bind(this); this.onTouchEnd = this.onTouchEnd.bind(this); } static propTypes = { onMove: PropTypes.func, onStop: PropTypes.func, x: PropTypes.number.isRequired, y: PropTypes.number.isRequired, gridX: PropTypes.number, gridY: PropTypes.number }; onStart(e) { const ref = ReactDOM.findDOMNode(this.handle); const body = document.body; const box = ref.getBoundingClientRect(); this.setState({ relX: e.pageX - (box.left + body.scrollLeft - body.clientLeft), relY: e.pageY - (box.top + body.scrollTop - body.clientTop) }); } onMove(e) { const x = Math.trunc((e.pageX - this.state.relX) / this.gridX) * this.gridX; const y = Math.trunc((e.pageY - this.state.relY) / this.gridY) * this.gridY; if (x !== this.state.x || y !== this.state.y) { this.setState({ x, y }); this.props.onMove && this.props.onMove(this.state.x, this.state.y); } } onMouseDown(e) { if (e.button !== 0) return; this.onStart(e); document.addEventListener('mousemove', this.onMouseMove); document.addEventListener('mouseup', this.onMouseUp); e.preventDefault(); } onMouseUp(e) { document.removeEventListener('mousemove', this.onMouseMove); document.removeEventListener('mouseup', this.onMouseUp); this.props.onStop && this.props.onStop(this.state.x, this.state.y); e.preventDefault(); } onMouseMove(e) { this.onMove(e); e.preventDefault(); } onTouchStart(e) { this.onStart(e.touches[0]); document.addEventListener('touchmove', this.onTouchMove, {passive: false}); document.addEventListener('touchend', this.onTouchEnd, {passive: false}); e.preventDefault(); } onTouchMove(e) { this.onMove(e.touches[0]); e.preventDefault(); } onTouchEnd(e) { document.removeEventListener('touchmove', this.onTouchMove); document.removeEventListener('touchend', this.onTouchEnd); this.props.onStop && this.props.onStop(this.state.x, this.state.y); e.preventDefault(); } render() { return <div onMouseDown={this.onMouseDown} onTouchStart={this.onTouchStart} style={{ position: 'absolute', left: this.state.x, top: this.state.y, touchAction: 'none' }} ref={(div) => { this.handle = div; }} > {this.props.children} </div>; } } export default Draggable;
źródło
Reag-draggable jest również łatwy w użyciu. Github:
https://github.com/mzabriskie/react-draggable
import React, {Component} from 'react'; import ReactDOM from 'react-dom'; import Draggable from 'react-draggable'; var App = React.createClass({ render() { return ( <div> <h1>Testing Draggable Windows!</h1> <Draggable handle="strong"> <div className="box no-cursor"> <strong className="cursor">Drag Here</strong> <div>You must click my handle to drag me</div> </div> </Draggable> </div> ); } }); ReactDOM.render( <App />, document.getElementById('content') );
I mój index.html:
<html> <head> <title>Testing Draggable Windows</title> <link rel="stylesheet" type="text/css" href="style.css" /> </head> <body> <div id="content"></div> <script type="text/javascript" src="bundle.js" charset="utf-8"></script> <script src="http://localhost:8080/webpack-dev-server.js"></script> </body> </html>
Potrzebujesz ich stylów, które są krótkie lub nie uzyskasz oczekiwanego zachowania. Zachowanie podoba mi się bardziej niż niektóre inne możliwe wybory, ale jest też coś, co nazywa się możliwością zmiany rozmiaru i przeniesienia . Próbuję zmienić rozmiar, pracując z draggable, ale jak dotąd nie ma radości.
źródło
Oto prosty nowoczesne podejście do tego z
useState
,useEffect
iuseRef
w ES6.import React, { useRef, useState, useEffect } from 'react' const quickAndDirtyStyle = { width: "200px", height: "200px", background: "#FF9900", color: "#FFFFFF", display: "flex", justifyContent: "center", alignItems: "center" } const DraggableComponent = () => { const [pressed, setPressed] = useState(false) const [position, setPosition] = useState({x: 0, y: 0}) const ref = useRef() // Monitor changes to position state and update DOM useEffect(() => { if (ref.current) { ref.current.style.transform = `translate(${position.x}px, ${position.y}px)` } }, [position]) // Update the current position if mouse is down const onMouseMove = (event) => { if (pressed) { setPosition({ x: position.x + event.movementX, y: position.y + event.movementY }) } } return ( <div ref={ ref } style={ quickAndDirtyStyle } onMouseMove={ onMouseMove } onMouseDown={ () => setPressed(true) } onMouseUp={ () => setPressed(false) }> <p>{ pressed ? "Dragging..." : "Press to drag" }</p> </div> ) } export default DraggableComponent
źródło
Oto odpowiedź na rok 2020 z hakiem:
function useDragging() { const [isDragging, setIsDragging] = useState(false); const [pos, setPos] = useState({ x: 0, y: 0 }); const ref = useRef(null); function onMouseMove(e) { if (!isDragging) return; setPos({ x: e.x - ref.current.offsetWidth / 2, y: e.y - ref.current.offsetHeight / 2, }); e.stopPropagation(); e.preventDefault(); } function onMouseUp(e) { setIsDragging(false); e.stopPropagation(); e.preventDefault(); } function onMouseDown(e) { if (e.button !== 0) return; setIsDragging(true); setPos({ x: e.x - ref.current.offsetWidth / 2, y: e.y - ref.current.offsetHeight / 2, }); e.stopPropagation(); e.preventDefault(); } // When the element mounts, attach an mousedown listener useEffect(() => { ref.current.addEventListener("mousedown", onMouseDown); return () => { ref.current.removeEventListener("mousedown", onMouseDown); }; }, [ref.current]); // Everytime the isDragging state changes, assign or remove // the corresponding mousemove and mouseup handlers useEffect(() => { if (isDragging) { document.addEventListener("mouseup", onMouseUp); document.addEventListener("mousemove", onMouseMove); } else { document.removeEventListener("mouseup", onMouseUp); document.removeEventListener("mousemove", onMouseMove); } return () => { document.removeEventListener("mouseup", onMouseUp); document.removeEventListener("mousemove", onMouseMove); }; }, [isDragging]); return [ref, pos.x, pos.y, isDragging]; }
Następnie komponent korzystający z haka:
function Draggable() { const [ref, x, y, isDragging] = useDragging(); return ( <div ref={ref} style={{ position: "absolute", width: 50, height: 50, background: isDragging ? "blue" : "gray", left: x, top: y, }} ></div> ); }
źródło
Chciałbym dodać trzeci scenariusz
Ruchoma pozycja nie jest w żaden sposób zapisywana. Pomyśl o tym jak o ruchu myszy - Twój kursor nie jest elementem React, prawda?
Wszystko, co musisz zrobić, to dodać do komponentu właściwość typu „draggable” i strumień zdarzeń związanych z przeciąganiem, które będą manipulować domeną.
setXandY: function(event) { // DOM Manipulation of x and y on your node }, componentDidMount: function() { if(this.props.draggable) { var node = this.getDOMNode(); dragStream(node).onValue(this.setXandY); //baconjs stream }; },
W tym przypadku manipulacja DOM jest elegancką rzeczą (nigdy nie myślałem, że to powiem)
źródło
setXandY
funkcję wyimaginowanym komponentem?Zaktualizowałem klasę za pomocą referencji, ponieważ wszystkie rozwiązania, które tu widzę, mają rzeczy, które nie są już obsługiwane lub wkrótce zostaną zdeprecjonowane, takie jak
ReactDOM.findDOMNode
. Może być rodzicem elementu podrzędnego lub grupy dzieci :)import React, { Component } from 'react'; class Draggable extends Component { constructor(props) { super(props); this.myRef = React.createRef(); this.state = { counter: this.props.counter, pos: this.props.initialPos, dragging: false, rel: null // position relative to the cursor }; } /* we could get away with not having this (and just having the listeners on our div), but then the experience would be possibly be janky. If there's anything w/ a higher z-index that gets in the way, then you're toast, etc.*/ componentDidUpdate(props, state) { if (this.state.dragging && !state.dragging) { document.addEventListener('mousemove', this.onMouseMove); document.addEventListener('mouseup', this.onMouseUp); } else if (!this.state.dragging && state.dragging) { document.removeEventListener('mousemove', this.onMouseMove); document.removeEventListener('mouseup', this.onMouseUp); } } // calculate relative position to the mouse and set dragging=true onMouseDown = (e) => { if (e.button !== 0) return; let pos = { left: this.myRef.current.offsetLeft, top: this.myRef.current.offsetTop } this.setState({ dragging: true, rel: { x: e.pageX - pos.left, y: e.pageY - pos.top } }); e.stopPropagation(); e.preventDefault(); } onMouseUp = (e) => { this.setState({ dragging: false }); e.stopPropagation(); e.preventDefault(); } onMouseMove = (e) => { if (!this.state.dragging) return; this.setState({ pos: { x: e.pageX - this.state.rel.x, y: e.pageY - this.state.rel.y } }); e.stopPropagation(); e.preventDefault(); } render() { return ( <span ref={this.myRef} onMouseDown={this.onMouseDown} style={{ position: 'absolute', left: this.state.pos.x + 'px', top: this.state.pos.y + 'px' }}> {this.props.children} </span> ) } } export default Draggable;
źródło