रिएक्ट घटक / डिव ड्रैगजेबल बनाने का अनुशंसित तरीका


97

मैं एक ड्रैगेबल (जो कि माउस द्वारा रिपोजिबल है) रिएक्ट कंपोनेंट बनाना चाहता हूं, जो जरूरी तौर पर ग्लोबल स्टेट और बिखरे हुए इवेंट हैंडलर को शामिल करता है। मैं इसे अपने जेएस फ़ाइल में एक वैश्विक चर के साथ गंदे तरीके से कर सकता हूं, और शायद इसे एक अच्छे क्लोजर इंटरफेस में भी लपेट सकता हूं, लेकिन मैं जानना चाहता हूं कि क्या कोई ऐसा तरीका है जो प्रतिक्रिया के साथ बेहतर हो।

इसके अलावा, चूंकि मैंने पहले कभी कच्चे जावास्क्रिप्ट में ऐसा नहीं किया है, मैं यह देखना चाहता हूं कि एक विशेषज्ञ यह कैसे करता है, यह सुनिश्चित करने के लिए कि मैंने सभी कोने के मामलों को संभाला है, खासकर जब वे रिएक्ट से संबंधित हैं।

धन्यवाद।


असल में, मैं कम से कम गद्य स्पष्टीकरण के साथ कोड के रूप में खुश हूं, या यहां तक ​​कि बस, "आप इसे ठीक कर रहे हैं"। लेकिन यहाँ मेरे काम का एक JSFiddle अब तक है: jsfiddle.net/Z2JtM
एंड्रयू फ़्लेनर

मैं मानता हूं कि यह एक वैध प्रश्न है, यह देखते हुए कि वर्तमान में देखने के लिए प्रतिक्रिया कोड के बहुत कम उदाहरण हैं
जारेड फोर्सिथ

1
मेरे उपयोग के मामले के लिए एक सरल HTML5 समाधान मिला - youtu.be/z2nHLfiiKBA । किसी की मदद कर सकते हैं !!
प्रेम

इसे इस्तेमाल करे। यह एक साधारण HOC है जो तत्वों को ड्रैग
Shan

जवाबों:


111

मुझे शायद इसे ब्लॉग पोस्ट में बदल देना चाहिए, लेकिन यहाँ इसका ठोस उदाहरण है।

टिप्पणियों को अच्छी तरह से चीजों को समझाना चाहिए, लेकिन मुझे बताएं कि क्या आपके पास प्रश्न हैं।

और यहाँ के साथ खेलने के लिए पहेली है: 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))
  }
})

राज्य के स्वामित्व आदि पर विचार।

"किसके पास खुद का राज्य होना चाहिए" जवाब देने के लिए एक महत्वपूर्ण सवाल है, शुरू से ही सही। "ड्रैगेबल" घटक के मामले में, मैं कुछ अलग परिदृश्य देख सकता था।

दृष्टांत 1

माता-पिता को ड्रैगबल की वर्तमान स्थिति का स्वामी होना चाहिए। इस मामले में, ड्रैगगबल अभी भी "आई एम ड्रैगिंग" स्थिति का मालिक होगा, लेकिन कॉल करेगाthis.props.onChange(x, y) जब भी कोई मूसमव इवेंट होता है ।

दृश्य २

माता-पिता को केवल "नॉन-मूविंग पोजिशन" के मालिक होने की जरूरत है, और इसलिए ड्रैग करने योग्य के लिए यह "ड्रैगिंग पोजिशन" होगा, लेकिन ऑनमुगअप इसे कॉल करेगा this.props.onChange(x, y) अभिभावक के लिए अंतिम निर्णय को और टाल देगा। यदि माता-पिता को यह पसंद नहीं है कि ड्रैगगबल कहां समाप्त हुआ, तो यह अपडेट नहीं होगा कि यह स्थिति है, और ड्रैग करने योग्य को खींचने से पहले प्रारंभिक स्थिति में "स्नैप बैक" कर देगा।

मिश्रण या घटक?

@ लॉरेलन ने बताया कि, क्योंकि "ड्रैगेबल" अपने आप में एक चीज़ से ज्यादा एक विशेषता है, यह एक मिक्सिन के रूप में बेहतर हो सकता है। मिक्सिंस के साथ मेरा अनुभव सीमित है, इसलिए मैंने यह नहीं देखा कि वे जटिल परिस्थितियों में कैसे मदद कर सकते हैं या रास्ते में आ सकते हैं। यह अच्छी तरह से सबसे अच्छा विकल्प हो सकता है।


4
महान उदाहरण है। यह Mixinएक पूर्ण वर्ग की तुलना में अधिक उपयुक्त लगता है क्योंकि "ड्रैगेबल" वास्तव में एक वस्तु नहीं है, यह एक वस्तु की क्षमता है।
रॉस एलन

2
मैं इसके साथ थोड़ा बहुत खेलता था, ऐसा लगता है कि इसके माता-पिता के बाहर खींचने से कुछ भी नहीं होता है, लेकिन सभी प्रकार की अजीब चीजें तब होती हैं जब इसके दूसरे प्रतिक्रिया घटक में घसीटा जाता है
गोर्केम युर्टसेवन

11
आप कर के द्वारा jquery निर्भरता को हटा सकते हैं: var computedStyle = window.getComputedStyle(this.getDOMNode()); pos = { top: parseInt(computedStyle.top), left: parseInt(computedStyle.left) }; यदि आप प्रतिक्रिया के साथ jquery का उपयोग कर रहे हैं, तो आप शायद कुछ गलत कर रहे हैं;) अगर आपको कुछ jquery प्लगइन की आवश्यकता है, तो मुझे लगता है कि यह आम तौर पर आसान है और शुद्ध प्रतिक्रिया में इसे फिर से लिखने के लिए कम कोड है।
मैट क्रिंकलाव-वोगट

7
बस @ मैटक्रिंकलॉव-वोग्ट द्वारा उपरोक्त टिप्पणी पर अनुवर्ती कार्रवाई करने के लिए कहना चाहता था कि एक अधिक बुलेट-प्रूफ समाधान का उपयोग करना है this.getDOMNode().getBoundingClientRect()- getComputedStyle किसी भी वैध सीएसएस संपत्ति को आउटपुट कर सकता autoहै जिसमें उपरोक्त कोड का परिणाम होगा NaN। MDN लेख देखें: developer.mozilla.org/en-US/docs/Web/API/Element/…
Andru

2
और फिर से पालन करने के लिए this.getDOMNode(), कि पदावनत किया गया है। डोम नोड प्राप्त करने के लिए रेफरी का उपयोग करें। facebook.github.io/react/docs/…
क्रिस Sattinger

63

मैंने रिएक्शन-डंड लागू किया फुल डोम कंट्रोल के साथ रिएक्ट के लिए रिएक्ट , एक लचीला एचटीएमएल 5 ड्रैग-एंड-ड्रॉप मिक्सिन लागू किया।

मौजूदा ड्रैग-एंड-ड्रॉप लाइब्रेरीज़ मेरे उपयोग के मामले में फिट नहीं थीं इसलिए मैंने अपना लिखा। यह उस कोड के समान है जिसे हम Stampsy.com पर लगभग एक साल से चला रहे हैं, लेकिन रिएक्ट और फ्लक्स का लाभ उठाने के लिए फिर से लिखा गया है।

मेरे पास प्रमुख आवश्यकताएं:

  • शून्य डोम या सीएसएस का स्वयं का उत्सर्जन करें, इसे उपभोग करने वाले घटकों के लिए छोड़ दें;
  • खपत करने वाले घटकों पर जितना संभव हो उतना कम संरचना का प्रस्ताव;
  • एचटीएमएल 5 को प्राथमिक बैकेंड के रूप में खींचें और छोड़ें, लेकिन भविष्य में अलग-अलग बैकेंड जोड़ना संभव बना सकते हैं;
  • मूल HTML5 एपीआई की तरह, डेटा को खींचने पर जोर देना और न कि केवल "ड्रैग करने योग्य विचार";
  • उपभोग कोड से एचटीएमएल 5 एपीआई को छिपाएं;
  • विभिन्न प्रकार के डेटा के लिए विभिन्न घटक "ड्रैग सोर्स" या "ड्रॉप टारगेट" हो सकते हैं;
  • एक घटक को कई ड्रैग सोर्स रखने और जरूरत पड़ने पर लक्ष्य छोड़ने की अनुमति दें;
  • यदि संगत डेटा को खींचा जा रहा है या होवर किया गया है, तो उनकी उपस्थिति को बदलने के लिए ड्रॉप लक्ष्य बनाना आसान बनाएं;
  • ब्राउज़र स्क्रीनशॉट को दरकिनार करते हुए, तत्व स्क्रीनशॉट के बजाय ड्रैग थंबनेल के लिए छवियों का उपयोग करना आसान बनाएं।

यदि ये ध्वनि आपको परिचित हैं, तो पढ़ें।

प्रयोग

सरल खींचें स्रोत

सबसे पहले, उन प्रकार के डेटा की घोषणा करें जिन्हें खींचा जा सकता है।

इनका उपयोग ड्रैग सोर्स और ड्रॉप टारगेट की "अनुकूलता" जांचने के लिए किया जाता है:

// ItemTypes.js
module.exports = {
  BLOCK: 'block',
  IMAGE: 'image'
};

(यदि आपके पास एकाधिक डेटा प्रकार नहीं हैं, तो यह परिवाद आपके लिए नहीं हो सकता है।)

फिर, चलो एक बहुत ही सरल ड्रैग करने योग्य घटक बनाते हैं, जब घसीटा जाता है, प्रतिनिधित्व करता है 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)} />
    );
  }
);

निर्दिष्ट करके configureDragDrop, हम DragDropMixinइस घटक के ड्रैग-ड्रॉप व्यवहार को बताते हैं। ड्रैग करने योग्य और टपकने वाले दोनों घटक एक ही मिश्रण का उपयोग करते हैं।

अंदर configureDragDrop, हमें registerTypeअपने प्रत्येक कस्टम के लिए कॉल करना होगा ItemTypesजो घटक का समर्थन करता है। उदाहरण के लिए, आपके ऐप में छवियों के कई प्रतिनिधित्व हो सकते हैं, और प्रत्येक एक के dragSourceलिए प्रदान करेगाItemTypes.IMAGE

A dragSourceसिर्फ एक ऑब्जेक्ट है जो निर्दिष्ट करता है कि ड्रैग सोर्स कैसे काम करता है। आपको beginDragउस आइटम को वापस लागू करना होगा जो आपके द्वारा खींचे जा रहे डेटा का प्रतिनिधित्व करता है और, वैकल्पिक रूप से, कुछ विकल्प जो ड्रैगिंग UI को समायोजित करते हैं। आप वैकल्पिक रूप canDragसे ड्रैग करने, या endDrag(didDrop)ड्रॉप होने (या नहीं) होने पर कुछ लॉजिक निष्पादित करने के लिए मना कर सकते हैं । और आप घटकों के बीच इस तर्क dragSourceको उनके लिए एक साझा मिश्रण उत्पन्न करके साझा कर सकते हैं।

अंत में, आपको ड्रैग हैंडलर संलग्न करने के लिए {...this.dragSourceFor(itemType)}कुछ (एक या अधिक) तत्वों पर उपयोग करना होगा render। इसका मतलब है कि आपके पास एक तत्व में कई "ड्रैग हैंडल" हो सकते हैं, और वे विभिन्न प्रकार के आइटम के अनुरूप भी हो सकते हैं। (यदि आप JSX Spread Attributes Synax से परिचित नहीं हैं , तो इसे देखें)।

सरल ड्रॉप लक्ष्य

मान लीजिए कि हम s के ImageBlockलिए एक टारगेट बनना चाहते हैं IMAGE। यह बहुत अधिक समान है, सिवाय इसके कि हमें registerTypeएक dropTargetकार्यान्वयन देने की आवश्यकता है :

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>
    );
  }
);

खींचें स्रोत + ड्रॉप लक्ष्य एक घटक में

मान लें कि अब हम चाहते हैं कि उपयोगकर्ता एक छवि को बाहर निकालने में सक्षम हो ImageBlock। हमें बस dragSourceइसमें और कुछ हैंडलर जोड़ने की आवश्यकता है :

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>
    );
  }
);

क्या संभव है?

मैंने सब कुछ कवर नहीं किया है लेकिन इस API का उपयोग कुछ और तरीकों से करना संभव है:

  • का उपयोग करें getDragState(type)औरgetDropState(type) यदि खींच सक्रिय है और इसका इस्तेमाल सीएसएस वर्ग या गुण टॉगल करने के लिए सीखने के लिए;
  • निर्दिष्ट dragPreviewहोना करने के लिए Imageखींचें प्लेसहोल्डर के रूप में छवियों (उपयोग का उपयोग करने के ImagePreloaderMixinलिए उन्हें लोड करने के लिए);
  • कहते हैं, हम ImageBlocksपुन: प्रयोज्य बनाना चाहते हैं । हम केवल उन्हें लागू करने की जरूरत है dropTargetऔर dragSourceके लिए ItemTypes.BLOCK
  • मान लीजिए कि हम अन्य प्रकार के ब्लॉक जोड़ते हैं। हम उनके पुनः तर्क तर्क को मिक्सी में रखकर पुनः उपयोग कर सकते हैं।
  • dropTargetFor(...types) कई प्रकारों को एक साथ निर्दिष्ट करने की अनुमति देता है, इसलिए एक ड्रॉप ज़ोन कई अलग-अलग प्रकारों को पकड़ सकता है।
  • जब आपको अधिक महीन दाने वाले नियंत्रण की आवश्यकता होती है, तो अधिकांश तरीकों को ड्रैग इवेंट पास किया जाता है जो उन्हें अंतिम पैरामीटर के रूप में उत्पन्न करता है।

अप-टू-डेट प्रलेखन और स्थापना निर्देशों के लिए, सिर के लिए प्रतिक्रिया- dnd रेपो गितुब पर


5
एक माउस का उपयोग करने के अलावा क्या ड्रैग एंड ड्रॉप और माउस ड्रैगिंग आम है? आपका उत्तर एक प्रश्न से संबंधित नहीं है और स्पष्ट रूप से एक पुस्तकालय विज्ञापन है।
पोलकोव्निकोव .ph

5
ऐसा लगता है कि 29 लोगों ने इसे प्रश्न से संबंधित पाया। रिएक्ट डीआईएनडी आपको माउस को सिर्फ ठीक से खींचने पर भी लागू करता है। मैं अपने काम को मुफ्त में साझा करने और उदाहरणों और विस्तारक दस्तावेज़ीकरण पर अगली बार काम करने से बेहतर समझूंगा ताकि मुझे स्नार्क टिप्पणियां पढ़ने में समय बिताना न पड़े।
दान अब्रामोव

7
हां, मैं पूरी तरह से जानता हूं। तथ्य यह है कि आपके पास कहीं और प्रलेखन है इसका मतलब यह नहीं है कि यह दिए गए प्रश्न का उत्तर है। आपने उसी परिणाम के लिए "Google का उपयोग करें" लिखा होगा। 29 उत्थान किसी ज्ञात व्यक्ति के लंबे पद के कारण होते हैं, उत्तर की गुणवत्ता के कारण नहीं।
polkovnikov.ph

प्रतिक्रिया-dnd के साथ स्वतंत्र रूप से ड्रैग करने योग्य सामान के आधिकारिक उदाहरणों के लिए अपडेट किए गए लिंक: मूल , उन्नत
uryga

23

Jared Forsyth का जवाब बेहद गलत और पुराना है। यह इस तरह के रूप antipatterns का पूरा सेट इस प्रकार के उपयोगstopPropagation , रंगमंच की सामग्री से आरंभ राज्य , jQuery का उपयोग, राज्य में नेस्टेड वस्तुओं और कुछ अजीब है draggingराज्य क्षेत्र। यदि फिर से लिखा जा रहा है, तो समाधान निम्नलिखित होगा, लेकिन यह अभी भी हर माउस के मूव टिक पर वर्चुअल डोम के सामंजस्य के लिए मजबूर करता है और यह बहुत अच्छा नहीं है।

युपीडी। मेरा उत्तर बहुत गलत और पुराना था। अब कोड देशी रिएक्टर हैंडलर और स्टाइल अपडेट का उपयोग करके धीमी प्रतिक्रिया घटक जीवनचक्र के मुद्दों को कम करता है, उपयोग करता है transformक्योंकि इससे रिफ्लोज़ नहीं होता है, और थ्रॉटल डोम के माध्यम से बदलता है requestAnimationFrame। अब यह लगातार 60 एफपीएस है मेरे लिए हर ब्राउज़र में मैंने कोशिश की।

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'),
);

और सीएसएस का एक सा

.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;
}

JSFiddle पर उदाहरण।


2
इसके लिए धन्यवाद, यह निश्चित रूप से सबसे अच्छा समाधान नहीं है, लेकिन यह आज अनुप्रयोगों के निर्माण की सर्वोत्तम प्रथाओं का पालन करता है।
Spets

1
@ryanj Nope, डिफ़ॉल्ट मान खराब हैं, यही समस्या है। प्रॉप्स बदलने पर उचित कार्रवाई क्या है? क्या हमें राज्य को नए डिफ़ॉल्ट पर रीसेट करना चाहिए? क्या हमें नए डिफ़ॉल्ट मान की तुलना राज्य में डिफ़ॉल्ट रूप से रीसेट करने के लिए पुराने डिफ़ॉल्ट मान से करनी चाहिए जब डिफ़ॉल्ट में बदलाव हुआ? उपयोगकर्ता को केवल निरंतर मूल्य का उपयोग करने के लिए प्रतिबंधित करने का कोई तरीका नहीं है, और कुछ नहीं। इसलिए यह एक एंटीपैटर्न है। डिफ़ॉल्ट मानों को स्पष्ट रूप से उच्च-क्रम के घटकों (अर्थात पूरी कक्षा के लिए, किसी वस्तु के लिए नहीं) के माध्यम से बनाया जाना चाहिए, और कभी भी प्रॉपर सेट नहीं करना चाहिए।
पॉल्कोवनिकोव .ph

1
मैं सम्मानपूर्वक असहमत हूं - घटक राज्य डेटा को संग्रहीत करने के लिए एक उत्कृष्ट स्थान है जो एक घटक के यूआई के लिए विशिष्ट है, जिसकी समग्र रूप से ऐप के लिए कोई प्रासंगिकता नहीं है। संभावित रूप से डिफ़ॉल्ट मानों को कुछ उदाहरणों में प्रॉप्स के रूप में पारित करने में सक्षम होने के बिना, इस डेटा पोस्ट-माउंट को पुनर्प्राप्त करने के विकल्प सीमित हैं और कई (सबसे अधिक) परिस्थितियों में घटक की आसपास की योनि से कम वांछनीय है जो संभावित रूप से एक अलग someDefaultVideue प्रोप से गुजर रहे हैं? बाद की तिथि। Im इसे सबसे अच्छा अभ्यास या किसी भी चीज़ के रूप में वकालत नहीं कर रहा हूँ, यह उतना ही हानिकारक नहीं है जितना कि आप imo का सुझाव दे रहे हैं
ryan j

2
वास्तव में बहुत सरल और सुरुचिपूर्ण समाधान। मुझे यह देखकर ख़ुशी हुई कि इस पर मेरा टेक कुछ ऐसा ही था। मेरे पास एक प्रश्न है: आप खराब प्रदर्शन का उल्लेख करते हैं, आप प्रदर्शन के साथ एक समान सुविधा प्राप्त करने का क्या प्रस्ताव रखेंगे?
गिलाउम एम

1
वैसे भी अब हमारे पास हुक हैं, और मुझे जल्द ही एक बार फिर से जवाब अपडेट करना होगा।
पॉल्कोवनिकोव .ph

13

मैंने 16 / ES6 को रिएक्ट करने के लिए polkovnikov.ph सॉल्यूशन अपडेट किया है जैसे कि टच हैंडलिंग और ग्रिड में स्नैप करना जो मुझे एक गेम की आवश्यकता है। ग्रिड में तड़कने से प्रदर्शन संबंधी समस्याएं दूर हो जाती हैं।

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;

hi @anyhotcountry क्या आप ग्रिडएक्स गुणांक का उपयोग करते हैं?
एलेक्सनिकोव

1
@AlexNikonov यह x दिशा में (स्नैप-इन) ग्रिड का आकार है। प्रदर्शन को बेहतर बनाने के लिए ग्रिडएक्स और ग्रिडवाई> 1 की सिफारिश की जाती है।
anyhotcountry

इसने मेरे लिए काफी अच्छा काम किया। OnStart () फ़ंक्शन में किए गए परिवर्तन पर: relX और rely की गणना करते हुए मैंने e.clienX-this.props.x का उपयोग किया। इसने मुझे ड्रैग क्षेत्र को एक मूल कंटेनर के अंदर रखने की अनुमति दी, बल्कि पूरे पृष्ठ को ड्रैग क्षेत्र के आधार पर रखा। मुझे पता है कि यह एक देर से टिप्पणी है लेकिन धन्यवाद कहना चाहता था।
ज्योफ

11

प्रतिक्रिया-ड्रैग करने योग्य भी उपयोग करना आसान है। 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')
);

और मेरा 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>

आपको उनकी शैलियों की आवश्यकता है, जो कम है, या आपको काफी अपेक्षित व्यवहार नहीं मिलता है। मुझे कुछ अन्य संभावित विकल्पों की तुलना में व्यवहार अधिक पसंद है, लेकिन प्रतिक्रिया-पुन: प्रयोज्य और जंगम नामक कुछ भी है । मैं ड्रैग करने योग्य के साथ आकार बदलने की कोशिश कर रहा हूं, लेकिन अभी तक कोई खुशी नहीं है।


8

यहाँ इसके साथ एक सरल आधुनिक दृष्टिकोण है useState, useEffectऔर useRefES6 में।

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

यह यहाँ सबसे अद्यतित उत्तर प्रतीत होता है।
codyThompson

2

मैं एक 3 परिदृश्य जोड़ना चाहूंगा

चलती स्थिति को किसी भी तरह से बचाया नहीं जाता है। इसे माउस आंदोलन के रूप में सोचें - आपका कर्सर एक रिएक्ट-घटक नहीं है, है ना?

आपको बस इतना करना है, अपने कंपोनेंट में "ड्रैगेबल" जैसा प्रॉप जोड़ना है और ड्रैगिंग इवेंट्स की एक स्ट्रीम है जो डोम में हेरफेर करेगी।

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
    };
},

इस मामले में, एक डोम हेरफेर एक खूबसूरत चीज है (मैंने कभी नहीं सोचा था कि मैं यह कहूंगा)


1
क्या आप setXandYएक काल्पनिक घटक के साथ फ़ंक्शन भर सकते हैं ?
आइलिमिस्ट

0

मैंने refs का उपयोग करते हुए वर्ग को अद्यतन किया है क्योंकि मेरे द्वारा यहां देखे जाने वाले सभी समाधानों में ऐसी चीजें हैं जो अब समर्थित नहीं हैं या जल्द ही जैसे ह्रास हो जाएंगी ReactDOM.findDOMNode। एक बच्चे के घटक या बच्चों के एक समूह के लिए माता-पिता हो सकते हैं :)

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;
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.