Javascript 当我拖放元素时,为什么相邻元素会移动?

Javascript 当我拖放元素时,为什么相邻元素会移动?,javascript,reactjs,Javascript,Reactjs,问题 我正在创建一个玩家有一手牌的游戏。这些卡可以移动到地图上(使用Mapbox)。当一张卡在地图上移动并且满足某些先决条件时,它将被“放置”在地图的该位置 不幸的是,当我将一张有效的卡拖到地图上时,它会被“放置”在该位置上,但相邻的卡会从手部移动到放置卡的最后一个位置 我制作了一个关于当前行为的快速视频: 代码 前端是一个React应用程序,我使用vanilla javascript实现拖放功能。基本上,我有一个包含许多卡片的组件,名为ProjectCardsHand。这些卡是ProjectC

问题

我正在创建一个玩家有一手牌的游戏。这些卡可以移动到地图上(使用Mapbox)。当一张卡在地图上移动并且满足某些先决条件时,它将被“放置”在地图的该位置

不幸的是,当我将一张有效的卡拖到地图上时,它会被“放置”在该位置上,但相邻的卡会从手部移动到放置卡的最后一个位置

我制作了一个关于当前行为的快速视频:

代码

前端是一个React应用程序,我使用vanilla javascript实现拖放功能。基本上,我有一个包含许多卡片的组件,名为
ProjectCardsHand
。这些卡是
ProjectCard
组件。我正在使用MapBox在
App.js
中渲染一个有社区的城市地图

以下是我的代码的节略版本:

ProjectCardsHand.js

import React from 'react';
import ProjectCard from './ProjectCard';

function addEventListenersToCards(map, $this) {
    let container = document.querySelector("#project-cards-hand");
    let activeItem = null;
    let active = false;

    container.addEventListener("touchstart", dragStart, {once: false, passive: false, capture: false});
    container.addEventListener("touchend", dragEnd, {once: false, passive: false, capture: false});
    container.addEventListener("touchmove", drag, {once: false, passive: false, capture: false});

    container.addEventListener("mousedown", dragStart, {once: false, passive: false, capture: false});
    container.addEventListener("mouseup", dragEnd, {once: false, passive: false, capture: false});
    container.addEventListener("mousemove", drag, {once: false, passive: false, capture: false});

    function dragStart(e) {
      
      if ((e.target !== e.currentTarget)) {
        active = true;
        activeItem = null;

        // this is the item we are interacting with
        activeItem = e.target.closest('.project-card');

        if (activeItem !== null) {
          if (!activeItem.xOffset) {
            activeItem.xOffset = 0;
          }

          if (!activeItem.yOffset) {
            activeItem.yOffset = 0;
          }

          activeItem.initialX = e.clientX - activeItem.xOffset;
          activeItem.initialY = e.clientY - activeItem.yOffset;


          // Move the project card up by 180px to cancel out the hover effect.
          activeItem.style.bottom = '180px';

        }
      }
    }

    function dragEnd(e) {

      if (activeItem !== null) {

        activeItem.initialX = activeItem.currentX;
        activeItem.initialY = activeItem.currentY;
        let neighborhoods = '';
        let projectId = activeItem.id.replace('project-','');

        // If the project is moved to a valid neighborhood, process the assignment of the project
        // to that neighborhood. Otherwise, nothing should happen and the project card is returned to the hand.
        neighborhoods = map.queryRenderedFeatures([[e.clientX,e.clientY],[e.clientX,e.clientY]], {layers: ['hoods']});


        if (neighborhoods.length > 0) {
          let projects = $this.state.projects;

          // Check if there are still project cards left in the hand.
          if (projects.length > 0) {
            for (let i = 0; i < projects.length; i++) {
              if (projects[i].id === projectId) {

                // Extract the neighborhood name from the neighborhood data.
                projects[i].neighborhood = neighborhoods[0].properties.BU_NAAM;

                // Get the latitude and longitue from the map based on the X and Y coordinates of the cursor.
                let projectAssignLocation = map.unproject([e.clientX,e.clientY]);

                // Subtract the cost of the project from the budget. If the remaining budget is 0 or higher, assign
                // the project to the location and update the budget.
                if ($this.props.handleBudgetChange($this.props.budget, projects[i].impact.cost*-1)) {
                  $this.props.handleProjectAssign(neighborhoods[0].properties.OBJECTID, projects[i], projectAssignLocation, function() {

                    // Remove the project from the list of projects in the hand.
                    projects.splice(i, 1);
                    $this.setState({projects : projects});
                  });
                } else {
                  // If the project card is moved to an invalid location (i.e. not a neighborhood), put the card back in the hand.
                  let itemAtInitialX = activeItem.initialX === activeItem.currentX;
                  let itemAtInitialY = activeItem.initialY === activeItem.currentY;
                  if (!itemAtInitialX && !itemAtInitialY) {
                    setTranslate(0, 0, activeItem);
                    activeItem.style.bottom = '0px';
                  }
                }
              }
            }
          }
        }
      }

      // Clean up the active item; The project card is either placed on a neighborhood or put back in the hand.
      active = false;
      activeItem = null;
      return;
    }

    function drag(e) {

      if (active) {
        activeItem.currentX = e.clientX - activeItem.initialX;
        activeItem.currentY = e.clientY - activeItem.initialY;

        activeItem.xOffset = activeItem.currentX;
        activeItem.yOffset = activeItem.currentY;
        setTranslate(activeItem.currentX, activeItem.currentY, activeItem);
      }
    }

    function setTranslate(xPos, yPos, el) {
      el.style.transform = "translate3d(" + xPos + "px, " + yPos + "px, 0)";
    }
}

export default class ProjectCardsHand extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            map: {},
            // This contains an array of project objects. I've removed it in this example for clarity's sake.
            projects: []
        }
    }

    componentWillReceiveProps(newProps) {
        // The project cards hand recieves the map as properties so that it can be queried by
        // the projects when they are dragged onto neighborhoods.
        this.setState({
            map: newProps.map
        })
        addEventListenersToCards(newProps.map, this);

    }
     

    render() {
        const projects = this.state.projects;
        const projectList = projects.map((project) =>
          <ProjectCard project={project}/>
        );

        return (
          <div id="project-cards-hand" className="row justify-content-center">
            {projectList}      
          </div>
        )
    }
}
import React from 'react';
import mapboxgl from 'mapbox-gl';
import axios from "axios";
import ProjectCardsHand from './ProjectCardsHand';

mapboxgl.accessToken = 'myAccessTokenNotGonnaTellYou';

export default class App extends React.Component {
  constructor(props) {
      super(props);
      this.handleProjectAssign = this.handleProjectAssign.bind(this);
      this.handleBudgetChange = this.handleBudgetChange.bind(this);
      this.state = {
        lng: 4.3220,
        lat: 52.0377,
        zoom: 12,
        hoods: [],
        projects: [],
        currentYear: 2020,
        budget: 3000000,
        map: {},
        pitch: 0
      };
  }
  
  // Functionality to initialize the map and add mouse event listeners to it goes here. Assumption
  // is that this does not affect the behavior in this problem. hoods is an array of objects containing
  // the neighborhoods. I store these in a mongoDB database. And call them in the component.

  // Handle the assignment of a project to a neighborhood.
  handleProjectAssign(hoodId, project, projectAssignLocation, callback) {
    let hoods = this.state.hoods.map(hood => {
        if (hood.properties.OBJECTID === hoodId) {
            try {
                hood.properties.droughtModifier += parseInt(project.impact.drought);
                hood.properties.precipitationModifier += parseInt(project.impact.precipitation);
                hood.properties.heatModifier += parseInt(project.impact.heat);
                hood.properties.subsidenceModifier += parseInt(project.impact.subsidence);
                hood.properties.biodiversityModifier += parseInt(project.impact.biodiversity);
            } catch (err) {
                console.error("Unable to assign modifiers to hood", hoodId, "Error:", err);
            }
        }

        return {
            type: 'Feature',
            geometry: hood.geometry,
            properties: hood.properties
        };
    })

    this.state.map.getSource('hoods').setData({
        type: 'FeatureCollection',
        features: hoods
      });
    
    let projects = this.state.projects;

    projects.push({
      'type': 'Feature',
      'geometry': {
        'type': 'Point',
        'coordinates': [
          projectAssignLocation.lng,
          projectAssignLocation.lat
        ]
      },
      'properties': {
        'title': project.name
      }
    });

    this.setState({ projects: projects });

    this.state.map.getSource('projects').setData({
        type: 'FeatureCollection',
        features: this.state.projects
    });

    callback(); 
  }

  handleBudgetChange(budget, delta) {
    let newBudget = budget + delta;
    if ((newBudget) >= 0) {
      this.setState({budget: newBudget});
      return true;
    }
    return false;
  }

  componentDidMount() {
    const map = new mapboxgl.Map({
      container: this.mapContainer,
      style: 'mapbox://styles/mapbox/streets-v11',
      center: [this.state.lng, this.state.lat],
      zoom: this.state.zoom,
      pitch: this.state.pitch || 0
    });

    this.setState({map: map});

    try {
      axios.get("/api/v1/hoods").then((response) => {

        const hoods = response.data.data.map(hood => {
     
          return {
            type: 'Feature',
            geometry: hood.geometry,
            properties: hood.properties
          };
        });

        this.setState({hoods: hoods});

        // Load the map. I've commented out this function in this question to keep it brief.
        this.loadMap(hoods, map, this);

      });
    } catch (err) {
      console.error("Failed to fetch hoods data:",err);
    }
    
  }

  render() {
    const hoods = this.state.hoods;
    return (
      <div className="container">
        <div ref={el => this.mapContainer = el} className="mapContainer" >
        </div>
        <ProjectCardsHand 
          map = {this.state.map}
          budget = {this.state.budget}
          handleProjectAssign = {this.handleProjectAssign}
          handleBudgetChange = {this.handleBudgetChange}
           />
      </div>
    )
  }   
}
我尝试过的 我尝试了很多方法:

  • 要遵循本指南:
  • 将所有事件侦听器选项设置为true,并将它们全部设置为false()
  • 在组件的状态中创建一个标志,以指示道具已接收一次,从而避免多次分配事件侦听器。这确实减少了听众的数量,但并没有解决问题
  • drag
    dragStart
    dragEnd
    函数中设置标志,以检查
    activeItem
    是否是正在拖动的项目,但每次
    activeItem
    似乎都被设置为相邻项目,即使不应该为其调用
    drag
    函数

我很想知道我做错了什么。我怎样才能修复这个问题,使没有被拖动的项目卡保持不变?

我成功地修复了它,尽管它更像是一种解决方法。分配完一张牌后,我将整张牌循环,并将它们重置到起始位置:

// Make sure the remaining cards stay in the hand.
for (let i = 0; i < allProjectCards.length; i++) {
    setTranslate(0, 0, allProjectCards[i]);
    activeItem.style.bottom = '0px';
}
接下来,我要确保为被拖动的项重置xOffset和yOffset。这些值用于确定开始拖动卡时的起始位置:

// Clean up the active item; The project card is either placed on a neighborhood or put back in the hand.
activeItem.xOffset = 0;
activeItem.yOffset = 0;
总而言之,这感觉就像我在玩一张牌,然后把剩下的牌扔到地板上,再把它们捡起来,确保它们仍然在我手中


欢迎提供更好的答案。

视频的URL已断开-您可以编辑以放入正确的URL吗?谢谢。更改了视频链接:)
// Clean up the active item; The project card is either placed on a neighborhood or put back in the hand.
activeItem.xOffset = 0;
activeItem.yOffset = 0;