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