প্রতিক্রিয়া উপাদান / ডিভ টেনে নেওয়াযোগ্য করার প্রস্তাবিত উপায়


100

আমি একটি টেনে নিয়ে যেতে সক্ষম করতে চাই (যা মাউস দ্বারা প্রতিস্থাপনযোগ্য) প্রতিক্রিয়া উপাদান, যা সম্ভবত বিশ্বব্যাপী রাষ্ট্র এবং বিক্ষিপ্ত ইভেন্ট হ্যান্ডলারদের জড়িত বলে মনে হয়। আমি আমার জেএস ফাইলটিতে একটি বৈশ্বিক পরিবর্তনশীল সহ এটি নোংরা পথে করতে পারি এবং সম্ভবত এটি একটি দুর্দান্ত ক্লোজার ইন্টারফেসেও গুটিয়ে রাখতে পারি, তবে আমি জানতে চাই যে প্রতিক্রিয়াটির সাথে আরও ভালভাবে আবদ্ধ হওয়ার উপায় আছে কিনা।

এছাড়াও, যেহেতু আমি এর আগে কাঁচা জাভাস্ক্রিপ্টে এটি আগে কখনও করি নি, তাই বিশেষজ্ঞরা কীভাবে কাজ করে তা আমি দেখতে চাই, যাতে কর্নার সমস্ত কেসগুলি হ্যান্ডেল করা হয়েছে তা নিশ্চিত করার জন্য, বিশেষত তারা প্রতিক্রিয়ার সাথে সম্পর্কিত।

ধন্যবাদ


আসলে, আমি কোড হিসাবে গদ্যের ব্যাখ্যা, বা এমনকি, "আপনি এটি ভাল করছেন" দিয়ে কমপক্ষে খুশি হব। তবে এখন পর্যন্ত আমার কাজের একটি জেএসফিডাল এখানে রয়েছে: jsfiddle.net/Z2JtM
অ্যান্ড্রু

আমি সম্মত হই যে এটি বর্তমানে একটি বৈধ প্রশ্ন, প্রতিক্রিয়ার কোডের খুব কম উদাহরণ রয়েছে যা দেখার জন্য বর্তমানে রয়েছে
জ্যারেড ফোর্সিথ

4
পাওয়া আমার ব্যবহার ক্ষেত্রে জন্য একটি সহজ HTML5 এর সমাধান - youtu.be/z2nHLfiiKBA । কাউকে সাহায্য করতে পারে !!
প্রেম

এটা চেষ্টা কর. এনজিএমজেজেএস
শান

উত্তর:


114

আমার সম্ভবত এটি একটি ব্লগ পোস্টে পরিণত করা উচিত তবে এখানে বেশ শক্ত উদাহরণ।

মন্তব্যগুলিতে জিনিসগুলি বেশ ভালভাবে ব্যাখ্যা করা উচিত, তবে আপনার যদি প্রশ্ন থাকে তবে আমাকে জানান।

এবং এখানে খেলতে আসা ফ্রিডল: 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)ড্রাগেবলটি এখনও "আমি টেনে আনছি " স্থিতির মালিক হবে তবে যখনই মাউস সরানোর ঘটনা ঘটবে তখনই ফোন করবে ।

দৃশ্য 2

পিতামাতার কেবল "নন-মুভিং পজিশন" এর মালিক হওয়া প্রয়োজন, এবং তাই ড্রাগেবল তার "ড্রাগিং পজিশন" এর মালিক হতে পারে তবে অন-মাউসআপ এটি this.props.onChange(x, y)পিতামাতার কাছে চূড়ান্ত সিদ্ধান্ত স্থগিত করে এবং মুলতবি করতে পারে। যদি পিতা-মাতারা পছন্দ না করে যেখানে টেনে নিয়ে যাওয়া শেষ হয়েছিল, তবে এটি কেবল তার রাষ্ট্র আপডেট করে না, এবং টেনে নিয়ে যাওয়ার আগে টেনে আনে তার প্রাথমিক অবস্থানে "ফিরে আসে" "

মিক্সিন বা উপাদান?

@ এসোরালেন উল্লেখ করেছেন যে, যেহেতু "ড্রাগগ্রেবল" নিজেই কোনও জিনিসের চেয়ে বেশি গুণ, তাই এটি মিক্সিন হিসাবে আরও ভাল পরিবেশন করতে পারে। মিশ্রিনগুলির সাথে আমার অভিজ্ঞতা সীমিত, সুতরাং তারা কীভাবে জটিল পরিস্থিতিতে সহায়তা করতে পারে বা কীভাবে পেতে পারে তা আমি দেখিনি। এটি ভাল সেরা বিকল্প হতে পারে।


4
দুর্দান্ত উদাহরণ। এটি Mixinএকটি পূর্ণ শ্রেণীর চেয়ে যথাযথ বলে মনে হয় যেহেতু "ড্রাগনযোগ্য" আসলে কোনও বস্তু নয়, এটি কোনও সামগ্রীর দক্ষতা।
রস অ্যালেন

4
আমি এটির সাথে কিছুটা খেলেছি, মনে হচ্ছে এটির পিতামাতার বাইরে
টানাটানি

11
আপনি jquery নির্ভরতা সরিয়ে ফেলতে পারেন: var computedStyle = window.getComputedStyle(this.getDOMNode()); pos = { top: parseInt(computedStyle.top), left: parseInt(computedStyle.left) }; আপনি যদি প্রতিক্রিয়ার সাথে jquery ব্যবহার করে থাকেন তবে সম্ভবত আপনি কিছু ভুল করছেন;) আপনার যদি কিছু jquery প্লাগইন প্রয়োজন হয় তবে আমার মনে হয় এটি শুদ্ধ প্রতিক্রিয়াতে পুনরায় লেখার জন্য এটি সাধারণত সহজতর এবং কম কোড।
ম্যাট ক্রিঙ্কলা-ভোগ

7
কেবলমাত্র @ ম্যাটক্রিংক্লা-ভোগের উপরের মন্তব্যটি অনুসরণ করতে চেয়েছিলেন যে আরও একটি বুলেট-প্রুফ সমাধান ব্যবহার করা উচিত this.getDOMNode().getBoundingClientRect()- getComputesStyle কোনও বৈধ সিএসএস সম্পত্তি আউটপুট দিতে পারে যার autoমধ্যে উপরের কোডটি কোনও ক্ষেত্রে ফলস্বরূপ হবে NaN। এমডিএন নিবন্ধটি দেখুন: developer.mozilla.org/en-US/docs/Web/API/Element/…
Andru

4
এবং পুনরায় অনুসরণ করতে this.getDOMNode(), এটি হ্রাস করা হয়েছে। ডোম নোড পেতে একটি রেফ ব্যবহার করুন। ফেসবুক.
github.io/react/docs/…

65

আমি রিঅ্যাক্ট-ডিএনডি বাস্তবায়িত করেছি , সম্পূর্ণ ডিওএম নিয়ন্ত্রণের সাথে প্রতিক্রিয়াটির জন্য একটি নমনীয় এইচটিএমএল 5 ড্র্যাগ-অ্যান্ড-ড্রপ মিক্সিন।

বিদ্যমান ড্র্যাগ অ্যান্ড ড্রপ লাইব্রেরিগুলি আমার ব্যবহারের ক্ষেত্রে ফিট করে না তাই আমি আমার নিজের লেখা। এটি স্ট্যাম্পসি ডটকম-এ আমরা প্রায় এক বছর ধরে চালাচ্ছি এমন কোডের মতো, তবে প্রতিক্রিয়া এবং ফ্লাক্সের সুবিধা নিতে পুনরায় লিখে।

মূল প্রয়োজনীয়তাগুলি আমার ছিল:

  • এর নিজস্ব শূন্য ডিওএম বা সিএসএস নির্গত করুন, এটি গ্রাসকারী উপাদানগুলিতে রেখে দেয়;
  • গ্রাসকারী উপাদানগুলিতে যতটা সম্ভব কাঠামো চাপিয়ে দিন;
  • এইচটিএমএল 5 টানুন এবং প্রাথমিক ব্যাকএন্ড হিসাবে ড্রপ ব্যবহার করুন তবে ভবিষ্যতে বিভিন্ন ব্যাকএন্ড যুক্ত করা সম্ভব করে দিন;
  • আসল এইচটিএমএল 5 এপিআই-এর মতো, ডেটা টেনে আনার উপর জোর দিন এবং কেবল "ড্র্যাগেবল ভিউ" নয়;
  • গ্রাহক কোড থেকে এইচটিএমএল 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

dragSourceহ'ল কেবলমাত্র একটি অবজেক্ট যা নির্দিষ্ট করে ড্রাগ ড্রাগ কীভাবে কাজ করে। আপনি beginDragযে আইটেমটি টেনে আনছেন সেই ডেটার প্রতিনিধিত্ব করে এবং allyচ্ছিকভাবে, কয়েকটি বিকল্প যা ড্র্যাগিং ইউআই সামঞ্জস্য করে তা প্রত্যাবর্তন করতে আপনাকে অবশ্যই প্রয়োগ করতে হবে । আপনি canDragটেনে আনতে নিষেধ করতে , বা endDrag(didDrop)ড্রপটি ঘটে (বা হয়নি) কিছু যুক্তি সম্পাদন করতে optionচ্ছিকভাবে প্রয়োগ করতে পারেন । এবং আপনি এই যুক্তিটি উপাদানগুলির মধ্যে ভাগ করে নিতে পারেন dragSourceতাদের জন্য একটি ভাগ করা মিক্সিন জেনারেট করে।

অবশেষে, আপনাকে অবশ্যই ড্রাগ {...this.dragSourceFor(itemType)}(কিছু বা একাধিক) উপাদান ব্যবহার করতে হবে renderড্রাগ হ্যান্ডলার সংযুক্ত করার জন্য। এর অর্থ আপনার একটি উপাদানে বেশ কয়েকটি "ড্রাগন হ্যান্ডলগুলি" থাকতে পারে এবং এগুলি বিভিন্ন আইটেমের ধরণেরও হতে পারে। (আপনি যদি জেএসএক্স স্প্রেড অ্যাট্রিবিউট সিনট্যাক্সের সাথে পরিচিত না হন তবে এটি পরীক্ষা করে দেখুন)।

সাধারণ ড্রপ লক্ষ্যমাত্রা

ধরা যাক আমরা এর 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)শিখতে এবং এটি CSS ক্লাস বা বৈশিষ্ট্যগুলিতে টগল করতে ব্যবহার করুন;
  • চিত্রগুলি টানা স্থানধারক হিসাবে ব্যবহার dragPreviewকরতে হবে Imageতা নির্দিষ্ট করুন ( ImagePreloaderMixinসেগুলি লোড করার জন্য ব্যবহার করুন);
  • বলুন, আমরা ImageBlocksপুনরায় অর্ডারযোগ্য করতে চাই। আমাদের কেবল তাদের বাস্তবায়ন করা প্রয়োজন dropTargetএবং এর dragSourceজন্য ItemTypes.BLOCK
  • মনে করুন আমরা অন্যান্য ধরণের ব্লক যুক্ত করি। আমরা তাদের পুনর্বিন্যাস যুক্তিটিকে একটি মিশ্রণে রেখে পুনরায় ব্যবহার করতে পারি।
  • dropTargetFor(...types) এক সাথে একাধিক প্রকার নির্দিষ্ট করার অনুমতি দেয়, তাই একটি ড্রপ অঞ্চল অনেকগুলি বিভিন্ন ধরণের ধরতে পারে।
  • যখন আপনার আরও সূক্ষ্ম-নিয়ন্ত্রণযুক্ত নিয়ন্ত্রণের দরকার হয়, বেশিরভাগ পদ্ধতিগুলি টেনে নিয়ে যাওয়ার ঘটনাটি পাস করে যা তাদের শেষ প্যারামিটার হিসাবে তৈরি করে।

আপ-টু-ডেট ডকুমেন্টেশন এবং ইন্সটলেশন নির্দেশাবলীর জন্য, গিথুব-এ রি-টি-ডেন্ডো রেপোতে যান


6
মাউস ব্যবহার করা ছাড়া ড্রাগ এবং ড্রপ এবং মাউস টেনে আনার কী মিল রয়েছে? আপনার উত্তর কোনও প্রশ্নের সাথে সম্পর্কিত নয় এবং স্পষ্টভাবে একটি লাইব্রেরির বিজ্ঞাপন।
polkovnikov.ph

4
দেখে মনে হচ্ছে 29 জন এটি প্রশ্নের সাথে সম্পর্কিত বলে মনে হয়েছে। প্রতিক্রিয়া ডিএনডি আপনাকে মাউস টেনে আনতে খুব সূক্ষ্মভাবে প্রয়োগ করতে দেয় । আমি নিখরচায় আমার কাজটি ভাগ করে নেওয়ার চেয়ে আরও ভাল চিন্তা করব এবং পরের বার উদাহরণ এবং বিস্তৃত ডকুমেন্টেশনের উপর কাজ করব যাতে স্ন্যার্ক মন্তব্য পড়তে আমার সময় কাটাতে হবে না।
ড্যান আব্রামভ

7
হ্যাঁ, আমি এটি পুরোপুরি জানি। আপনার অন্য কোথাও ডকুমেন্টেশন রয়েছে এর অর্থ এই নয় যে এটি প্রদত্ত প্রশ্নের উত্তর। আপনি একই ফলাফলের জন্য "গুগল ব্যবহার করুন" লিখতে পারতেন। 29 টি upvotes একটি পরিচিত ব্যক্তির দীর্ঘ পোস্টের কারণে, উত্তরের মানের কারণে নয়।
polkovnikov.ph

অ্যাক্সেস-ডিএনডি সহ অবাধে ড্রাগেবল স্টাফের অফিসিয়াল উদাহরণগুলিতে আপডেট হওয়া লিঙ্কগুলি: বেসিক , অ্যাডভান্সড
অরিগা

@ ড্যানআব্রামভ পোস্টটি করার জন্য আপনার প্রচেষ্টাকে আমি প্রশংসা করি, এটি না হলে সম্ভবত আমি এত তাড়াতাড়ি প্রতিক্রিয়া-ডিএনডির দিকে চালিত হত না । আপনার অনেক পণ্যের মতো, এটি প্রথমে অযথা জটিল মনে হয় তবে প্রাথমিকভাবে পেশাদার পদ্ধতিতে একত্রিত হন (আপনার ডকুমেন্টেশনের মাধ্যমে আমি সাধারণত সফটওয়্যার ইঞ্জিনিয়ারিংয়ের বিষয়ে একটি বা দুটি জিনিস শিখি ...) আমি "যারা পড়ে না তারা পড়েছি জেনে নিন ইউনিক্স এটিকে পুনঃপ্রণালীকরণের জন্য ধ্বংসপ্রাপ্ত ... দুর্বলভাবে, "এবং আমি অনুভব করি যে আপনার অনেকগুলি সৃষ্টির ক্ষেত্রে এটি মিল থাকতে পারে (এটি সহ)।
নাথান চ্যাপেল

23

জ্যারেড ফোরসিথের উত্তর মারাত্মকভাবে ভুল এবং পুরানো। এটি পুরোপুরি অ্যান্টিপ্যাটার্নগুলি অনুসরণ করে যেমন ব্যবহার করাstopPropagation , প্রপস থেকে রাষ্ট্রের সূচনা করা , জিক্যুরির ব্যবহার, নেস্টেড অবজেক্টসগুলি স্টেটে এবং কিছু অদ্ভুত draggingরাষ্ট্র ক্ষেত্র রয়েছে। যদি নতুন করে লেখা হয় তবে সমাধানটি নিম্নলিখিত হবে but তবে এটি এখনও প্রতিটি মাউস মুভ টিকের উপর ভার্চুয়াল ডিওএম মিলনকে জোর করে এবং এটি খুব কার্য সম্পাদনকারী নয়।

ইউপিডি। আমার উত্তরটি ভয়াবহভাবে ভুল এবং পুরানো ছিল। এখন ধীর কোড বিপদ দূর করে সমস্যা নেটিভ ইভেন্ট হ্যান্ডলার এবং শৈলী আপডেট, ব্যবহারসমূহ ব্যবহার করে কম্পোনেন্ট জীবনচক্র প্রতিক্রিয়া transformযেমন reflows হতে নয়, এবং DOM মাধ্যমে পরিবর্তন throttles 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;
}

জেএসফিডেলের উপর উদাহরণ।


4
এর জন্য ধন্যবাদ, এটি অবশ্যই সর্বাধিক পারফরম্যান্ট সমাধান নয় তবে এটি আজ অ্যাপ্লিকেশন তৈরির সর্বোত্তম অনুশীলনগুলি অনুসরণ করে।
Spets

4
@ আরঞ্জ নোপ, ডিফল্ট মানগুলি খারাপ, এটাই সমস্যা। প্রপস পরিবর্তিত হলে যথাযথ পদক্ষেপটি কী? আমাদের কি নতুন ডিফল্টতে রাষ্ট্র পুনরায় সেট করা উচিত? যখন আমাদের ডিফল্ট পরিবর্তন হয় তখনই কি আমাদের নতুন ডিফল্ট মানটিকে একটি পুরানো ডিফল্ট মানের সাথে তুলনা করা উচিত? কেবলমাত্র একটি ধ্রুবক মান ব্যবহার করতে ব্যবহারকারীকে সীমাবদ্ধ করার কোনও উপায় নেই এবং অন্য কিছু নেই। এ কারণেই এটি একটি অ্যান্টিপ্যাটার্ন। ডিফল্ট মানগুলি উচ্চ-অর্ডার উপাদানগুলির মাধ্যমে স্পষ্টভাবে তৈরি করা উচিত (অর্থাত্ পুরো শ্রেণীর জন্য, কোনও বস্তুর জন্য নয়) এবং প্রপসের মাধ্যমে কখনই সেট করা উচিত নয়।
polkovnikov.ph

4
আমি শ্রদ্ধার সাথে একমত নই - উপাদান উপাদানটি কোনও উপাদানগুলির ইউআইয়ের সাথে সুনির্দিষ্ট ডেটা সংরক্ষণ করার জন্য একটি দুর্দান্ত জায়গা, উদাহরণস্বরূপ অ্যাপ্লিকেশনের সাথে তার কোনও প্রাসঙ্গিকতা নেই। কিছু কিছু ক্ষেত্রে প্রপস হিসাবে ডিফল্ট মানগুলি সম্ভাব্যরূপে পাস করতে সক্ষম না হয়ে, মাউন্ট পোস্টের পরে এই ডেটা পুনরুদ্ধারের বিকল্পগুলি সীমাবদ্ধ এবং অনেকগুলি (সবচেয়ে?) পরিস্থিতিতে কোনও উপাদানটির আশেপাশের অসচ্ছলতার চেয়ে কম কাঙ্ক্ষিত সম্ভাব্যভাবে একটি ভিন্ন কিছু ডিফল্টভ্যালু প্রপ পাস করা হচ্ছে a পরবর্তী তারিখ আমি এটিকে সেরা অনুশীলন বা বাছাইয়ের কোনও হিসাবে বিবেচনা করছি না, এটি ইমো প্রস্তাব দেওয়ার মতোই ক্ষতিকারক নয়
ryan j

4
সত্যিই খুব সহজ এবং মার্জিত সমাধান। আমি এটা দেখে আমার মতামতটি একই রকম হয়েছিল তা দেখে আমি আনন্দিত। আমার একটি প্রশ্ন আছে: আপনি দুর্বল পারফরম্যান্সের কথা উল্লেখ করেছেন, আপনি পারফরম্যান্স মাথায় রেখে অনুরূপ বৈশিষ্ট্য অর্জনের জন্য কী প্রস্তাব করবেন?
গিলাইম এম

4
যাইহোক আমাদের এখন হুক আছে এবং শীঘ্রই আমাকে আবার একটি উত্তর আপডেট করতে হবে।
polkovnikov.ph

14

টাচ হ্যান্ডলিং এবং গ্রিডে স্নেপিংয়ের মতো উন্নতি সহ আমি 16 / ES6 এর প্রতিক্রিয়াটির সাথে পোলকনিকভ.এফ সমাধানটি আপডেট করেছি যা একটি গেমের জন্য আমার প্রয়োজন। গ্রিডে স্ন্যাপ করা পারফরম্যান্সের সমস্যাগুলি হ্রাস করে।

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;

হাই @ কোন হটকাউন্ট্রি আপনি গ্রিডএক্স সহগের জন্য কী ব্যবহার করেন?
অ্যালেক্সনিকনভ

4
@ অ্যালেক্সনিকনভ এটি x দিকের (স্ন্যাপ টু) গ্রিডের আকার। কর্মক্ষমতা উন্নত করতে গ্রিডএক্স এবং গ্রিডওয়াই> 1 রাখার পরামর্শ দেওয়া হচ্ছে।
কোনও হটকাউন্ট্রি

এটি আমার পক্ষে বেশ ভাল কাজ করেছে। পরিবর্তনের সময় আমি অনস্টার্ট () ফাংশনটিতে তৈরি করেছি: রিলএক্স এবং রিলয় গণনা করে আমি e.clienX-this.prop.x ব্যবহার করেছি। এটি আমাকে পুরো পৃষ্ঠার টেনে নিয়ে যাওয়ার ক্ষেত্রের উপর নির্ভর করে প্যারেন্ট পাত্রে g আমি জানি এটি একটি দেরী মন্তব্য কিন্তু শুধু ধন্যবাদ বলতে চেয়েছিলেন।
জিওফ

11

প্রতিক্রিয়া-ড্রাগেবল ব্যবহার করাও সহজ। গিথুব:

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

এবং আমার সূচক 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>

আপনার তাদের স্টাইলগুলি দরকার যা সংক্ষিপ্ত বা আপনি প্রত্যাশিত আচরণটি পান না। আমি আচরণ অন্যান্য সম্ভাব্য পছন্দের কিছু চেয়ে বেশি পছন্দ করি, কিন্তু এছাড়াও আছে কিছু বলা সম্ভব প্রতিক্রিয়া-রিসাইজেবল-এবং-অস্থাবর । আমি ড্র্যাগেবলের সাথে কাজ করে পুনরায় আকার দেওয়ার চেষ্টা করছি, তবে এখন পর্যন্ত কোনও আনন্দ নেই।


9

এখানে এই একটি সহজ আধুনিক প্রবেশপথ 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

4
এটি এখানে সর্বাধিক আপ-টু ডেট উত্তর বলে মনে হচ্ছে।
কোডি টম্পসন

2

আমি একটি তৃতীয় দৃশ্যাবলী যুক্ত করতে চাই

চলমান অবস্থান কোনওভাবেই সংরক্ষণ করা যায় না। এটিকে মাউস আন্দোলন হিসাবে ভাবুন - আপনার কার্সারটি কোনও প্রতিক্রিয়া উপাদান নয়, তাই না?

আপনার যা কিছু করা যায় তা হ'ল আপনার উপাদানটিতে "ড্রাগেবল" এর মতো একটি প্রপ যোগ করা এবং টেনে আনার ইভেন্টগুলির একটি স্ট্রিম যা ডোমটিকে সামাল দেবে।

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

এই ক্ষেত্রে, একটি ডিওএম হেরফের একটি মার্জিত জিনিস (আমি কখনই ভাবিনি যে আমি এটি বলব)


4
আপনি setXandYএকটি কাল্পনিক উপাদান সঙ্গে ফাংশন পূরণ করতে পারেন ?
থেলিমিস্ট

2

এখানে একটি হুক সহ 2020 উত্তর:

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

তারপরে একটি উপাদান যা হুক ব্যবহার করে:


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

1

আমি এখানে যে সমস্ত সমাধান দেখি সেগুলি রেফ ব্যবহার করে ক্লাস আপডেট করেছি এবং এমন কিছু জিনিস রয়েছে যা আর সমর্থিত নয় বা শীঘ্রই এর মতো অবমাননিত হবে 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;

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.