Saya ingin membuat komponen React yang dapat diseret (yaitu, dapat diposisikan ulang oleh mouse), yang tampaknya melibatkan keadaan global dan penangan acara yang tersebar. Saya dapat melakukannya dengan cara yang kotor, dengan variabel global dalam file JS saya, dan mungkin bahkan dapat membungkusnya dalam antarmuka penutupan yang bagus, tetapi saya ingin tahu apakah ada cara yang cocok dengan React lebih baik.
Juga, karena saya belum pernah melakukan ini dalam JavaScript mentah sebelumnya, saya ingin melihat bagaimana seorang ahli melakukannya, untuk memastikan saya telah menangani semua kasus sudut, terutama yang berkaitan dengan React.
Terima kasih.
javascript
reactjs
Andrew Fleenor
sumber
sumber
Jawaban:
Saya mungkin harus mengubahnya menjadi posting blog, tetapi inilah contoh yang cukup solid.
Komentar harus menjelaskan semuanya dengan cukup baik, tetapi beri tahu saya jika Anda memiliki pertanyaan.
Dan inilah biola untuk dimainkan: 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)) } })
Pemikiran tentang kepemilikan negara, dll.
"Siapa yang harus memiliki negara bagian apa" adalah pertanyaan penting untuk dijawab, sejak awal. Dalam kasus komponen "yang dapat diseret", saya dapat melihat beberapa skenario berbeda.
skenario 1
induk harus memiliki posisi draggable saat ini. Dalam kasus ini, yang dapat diseret akan tetap memiliki status "sedang saya menyeret", tetapi akan memanggil
this.props.onChange(x, y)
setiap kali terjadi peristiwa mousemove.Skenario 2
induk hanya perlu memiliki "posisi tidak bergerak", sehingga yang dapat diseret akan memiliki "posisi seret", tetapi pada saat tikus itu akan memanggil
this.props.onChange(x, y)
dan menunda keputusan akhir kepada induk. Jika induk tidak suka di mana tempat draggable berakhir, itu hanya tidak akan memperbarui statusnya, dan draggable akan "kembali" ke posisi awal sebelum menyeret.Mixin atau komponen?
@ssorallen menunjukkan bahwa, karena "draggable" lebih merupakan atribut daripada benda itu sendiri, itu mungkin berfungsi lebih baik sebagai mixin. Pengalaman saya dengan mixin terbatas, jadi saya belum melihat bagaimana mereka dapat membantu atau menghalangi dalam situasi yang rumit. Ini mungkin pilihan terbaik.
sumber
Mixin
kelas penuh karena "Draggable" sebenarnya bukan sebuah objek, ini adalah kemampuan dari sebuah objek.var computedStyle = window.getComputedStyle(this.getDOMNode()); pos = { top: parseInt(computedStyle.top), left: parseInt(computedStyle.left) };
Jika Anda menggunakan jquery dengan react, Anda mungkin melakukan sesuatu yang salah;) Jika Anda memerlukan beberapa plugin jquery, saya merasa biasanya easer dan lebih sedikit kode untuk menulis ulang dalam reaksi murni.this.getDOMNode().getBoundingClientRect()
- getComputedStyle dapat menampilkan properti CSS yang valid termasukauto
dalam hal ini kode di atas akan menghasilkan aNaN
. Lihat artikel MDN: developer.mozilla.org/en-US/docs/Web/API/Element/…this.getDOMNode()
, itu sudah usang. Gunakan ref untuk mendapatkan node dom. facebook.github.io/react/docs/…Saya menerapkan react-dnd , mixin seret dan lepas HTML5 yang fleksibel untuk Bereaksi dengan kontrol DOM penuh.
Pustaka seret dan lepas yang ada tidak sesuai dengan kasus penggunaan saya, jadi saya menulisnya sendiri. Ini mirip dengan kode yang telah kami jalankan selama sekitar satu tahun di Stampsy.com, tetapi ditulis ulang untuk memanfaatkan React dan Flux.
Persyaratan utama yang saya miliki:
Jika ini terdengar asing bagi Anda, baca terus.
Pemakaian
Sumber Drag Sederhana
Pertama, nyatakan jenis data yang bisa diseret.
Ini digunakan untuk memeriksa "kompatibilitas" sumber seret dan target pelepasan:
// ItemTypes.js module.exports = { BLOCK: 'block', IMAGE: 'image' };
(Jika Anda tidak memiliki beberapa tipe data, perpustakaan ini mungkin bukan untuk Anda.)
Kemudian, mari kita buat komponen yang dapat diseret sangat sederhana yang, saat diseret, mewakili
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)} /> ); } );
Dengan menentukan
configureDragDrop
, kami memberi tahuDragDropMixin
perilaku seret-lepas dari komponen ini. Baik komponen yang dapat diseret maupun yang dapat dilepas menggunakan campuran yang sama.Di dalam
configureDragDrop
, kita perlu memanggilregisterType
setiap kebiasaan kitaItemTypes
yang didukung komponen. Misalnya, mungkin ada beberapa representasi gambar di aplikasi Anda, dan masing-masing akan menyediakandragSource
untukItemTypes.IMAGE
.A
dragSource
hanyalah sebuah objek yang menentukan cara kerja sumber seret. Anda harus mengimplementasikanbeginDrag
untuk mengembalikan item yang mewakili data yang Anda seret dan, secara opsional, beberapa opsi yang menyesuaikan UI menyeret. Anda dapat menerapkan secara opsionalcanDrag
untuk melarang menyeret, atauendDrag(didDrop)
untuk menjalankan beberapa logika saat penurunan telah (atau belum) terjadi. Dan Anda dapat membagikan logika ini di antara komponen dengan membiarkan mixin bersama membuatkandragSource
untuk mereka.Terakhir, Anda harus menggunakan
{...this.dragSourceFor(itemType)}
beberapa (satu atau lebih) elemenrender
untuk memasang penangan tarik. Ini berarti Anda dapat memiliki beberapa "pegangan seret" dalam satu elemen, dan mereka bahkan mungkin sesuai dengan jenis item yang berbeda. (Jika Anda tidak terbiasa dengan JSX Spread Attributes sintaks , periksalah).Target Jatuhkan Sederhana
Katakanlah kita ingin
ImageBlock
menjadi target penurunan untukIMAGE
s. Ini hampir sama, kecuali bahwa kita perlu memberikanregisterType
sebuahdropTarget
implementasi: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> ); } );
Seret Sumber + Jatuhkan Target Dalam Satu Komponen
Katakanlah kita sekarang ingin pengguna dapat menarik keluar gambar
ImageBlock
. Kami hanya perlu menambahkan yang sesuaidragSource
untuk itu dan beberapa penangan: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> ); } );
Apa Lagi yang Mungkin?
Saya belum membahas semuanya tetapi mungkin untuk menggunakan API ini dalam beberapa cara lagi:
getDragState(type)
dangetDropState(type)
untuk mempelajari apakah menyeret aktif dan menggunakannya untuk mengalihkan kelas atau atribut CSS;dragPreview
untukImage
menggunakan gambar sebagai tempat penampung seret (gunakanImagePreloaderMixin
untuk memuatnya);ImageBlocks
. Kami hanya membutuhkan mereka untuk diimplementasikandropTarget
dandragSource
untukItemTypes.BLOCK
.dropTargetFor(...types)
memungkinkan untuk menentukan beberapa jenis sekaligus, sehingga satu zona tetesan dapat menangkap berbagai jenis.Untuk dokumentasi terbaru dan instruksi instalasi, kunjungi repo react-dnd di Github .
sumber
Jawaban Jared Forsyth salah besar dan ketinggalan zaman. Ini mengikuti seluruh rangkaian antipattern seperti penggunaan
stopPropagation
, menginisialisasi status dari props , penggunaan jQuery, objek bersarang dalam status dan memiliki beberapadragging
bidang status ganjil . Jika ditulis ulang, solusinya adalah sebagai berikut, tetapi tetap memaksa rekonsiliasi DOM virtual pada setiap centang gerakan mouse dan tidak terlalu berkinerja.UPD. Jawaban saya sangat salah dan ketinggalan zaman. Sekarang kode tersebut mengurangi masalah siklus hidup komponen React yang lambat dengan menggunakan penangan kejadian asli dan pembaruan gaya, menggunakannya
transform
karena tidak mengarah ke perubahan posisi, dan menghambat perubahan DOMrequestAnimationFrame
. Sekarang secara konsisten 60 FPS untuk saya di setiap browser yang saya coba.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'), );
dan sedikit 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; }
Contoh di JSFiddle.
sumber
Saya telah memperbarui solusi polkovnikov.ph ke React 16 / ES6 dengan peningkatan seperti penanganan sentuh dan snap ke grid yang saya butuhkan untuk sebuah game. Memasukkan ke kisi meringankan masalah kinerja.
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;
sumber
react-draggable juga mudah digunakan. 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') );
Dan index.html saya:
<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>
Anda membutuhkan gaya mereka, yang pendek, atau Anda tidak mendapatkan perilaku yang diharapkan. Saya lebih menyukai perilakunya daripada beberapa kemungkinan pilihan lain, tetapi ada juga sesuatu yang disebut react-resizable-and-movable . Saya mencoba mengubah ukuran bekerja dengan dapat diseret, tetapi sejauh ini tidak ada kegembiraan.
sumber
Berikut pendekatan modern sederhana untuk ini dengan
useState
,useEffect
danuseRef
di 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
sumber
Saya ingin menambahkan file Skenario ke 3
Posisi bergerak tidak disimpan dengan cara apapun. Anggap saja sebagai gerakan mouse - kursor Anda bukan komponen React, bukan?
Yang Anda lakukan hanyalah menambahkan prop seperti "draggable" ke komponen Anda dan aliran peristiwa menyeret yang akan memanipulasi dom.
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 }; },
Dalam hal ini, manipulasi DOM adalah hal yang elegan (saya tidak pernah berpikir saya akan mengatakan ini)
sumber
setXandY
fungsi dengan komponen imajiner?Inilah jawaban 2020 dengan Hook:
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]; }
Kemudian komponen yang menggunakan pengait:
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> ); }
sumber
Saya telah memperbarui kelas menggunakan referensi karena semua solusi yang saya lihat di sini memiliki hal-hal yang tidak lagi didukung atau akan segera disusutkan
ReactDOM.findDOMNode
. Dapat menjadi orang tua untuk komponen anak atau sekelompok anak :)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;
sumber