滚动后反应Dnd不在位置

Posted

技术标签:

【中文标题】滚动后反应Dnd不在位置【英文标题】:React Dnd out of position after scroll 【发布时间】:2019-08-20 12:56:56 【问题描述】:

我正在使用react-beautiful-dnd 使表格行可拖动。 如果我从上到下拖动,则拖动工作正常,当我向上滚动页面时,它会偏离位置。 我不知道为什么。 另外,我没有发现 css 有什么奇怪的地方

我不知道为什么会这样,也不知道如何解决。 这是我的问题的一个例子。

这是我的代码:

import update from "immutability-helper";
import * as React from "react";
import  DragDropContext, Draggable, Droppable  from "react-beautiful-dnd";
import  WithNamespaces, withNamespaces  from "react-i18next";
import  toastr  from "react-redux-toastr";
import * as HttpHelper from "../../httpHelper";
import  FormState  from "../common/ValidatedForm";
type Props = WithNamespaces & 
  id: number;
  displayName: string;
  type: string;
  language: any;
;

interface Fields 
  columns: any;


type State = FormState<Fields> & 
  isLoading: boolean,
  canSave: boolean,
  isSaving: boolean,
  possibleTags: any,
  configTagModalActive: boolean,
  previewModalActive: boolean,
  activeTag: any
;
const getItemStyle = (isDragging: any, draggableStyle: any) => (
  ...draggableStyle,
  opacity: isDragging ? 1 : 1,
  boxShadow: "0px 0px 0px 1px #8b8b8b",
);
const shadowColor = "#a0a0a057";
const Column = (props: any) => 
  function findindex(val: any, pt: any) 
    const list = pt ? props.possibleTags : props.tags;
    return list.findIndex((item: any) => val == item.name);
  
  function findindexofhelptext(val: any, pt: any) 
    const list = pt;
    return list.findIndex((item: any) => val == item.language);
  
  return (
    <tr ref=props.provided.innerRef ...props.provided.draggableProps style=getItemStyle(props.snapshot.isDragging, props.provided.draggableProps.style) className="draggablerow " + (props.snapshot.isDragging ? "draggedrow" : "")  key=props.indexnr data-id=props.index >
      <td ...props.provided.dragHandleProps style=width: "50px", textAlign: "center", cursor: "move"><i className="fa fa-bars" style=lineHeight: "40px", fontSize: "24px"></i></td>
      <td style= textAlign: "center", width: "100px" >
        <input
          type="checkbox"
          className="flipswitch"
          id=props.index
          checked=props.export
          onChange=props.toggleVisible
        />
      </td>
      <td style=width: "350px" >
        <input
          type="text"
          name="caption"
          id=props.index
          className="form-control"
          value=props.caption
          onChange=props.onTextUpdate
          style=boxShadow: "2px 2px 3px 1px" + shadowColor
        />
      </td>
      <td style=width: "350px" >
        <input
          type="text"
          name="fieldname"
          id=props.index
          className="form-control"
          value=props.fieldname
          onChange=props.onTextUpdate
          style=boxShadow: "2px 2px 3px 1px" + shadowColor
        />
      </td>
      <td style=width: "400px">
        <div className="tags-input" style=tagInputStyle>
          Object.keys(props.tags).map((key, i) =>
            <div key=i className="tag" onClick=props.onConfigButtonClicked data-id=i data-parent=props.index>
              props.tags[i].name <i className="fa fa-trash" id=props.index data-key=i data-name=props.tags[i].name onClick=props.onDeleteTag style=float: "right" ></i>
            </div>
          )
        </div>
      </td>
      <td style= textAlign: "center", width: "100px" >
        <button onClick=() => props.onDeleteColumn(props.index) type="button" style=padding : "8px 16px", boxShadow: "2px 2px 2px 1px" + shadowColor  className="btn btn-danger btn-rounded"><i className="fa fa-trash"></i></button>
      </td>
      </tr>
  );
;
const reorder = (list: any, startIndex: any, endIndex: any) => 
  const result = Array.from(list);
  const [removed] = result.splice(startIndex, 1);
  result.splice(endIndex, 0, removed);
  return result;
;
interface SetColumnsResponse extends HttpHelper.ResponseData  columns: any; 

class CrmConnectorColumns extends React.Component<Props, State> 

  constructor(props: Props) 
    super(props);
    this.moveColumn = this.moveColumn.bind(this);
    this.state = 
      isLoading: true,
      isSaving: false,
      canSave: false,
      errorColor: "danger",
      fields:  columns: [] ,
      deleteModalActive: false,
      configTagModalActive: false,
      previewModalActive: false,
      activeTag: name: "", attributes: [name: "", value: ""],
      possibleTags: [
        name: "PRIMARY", status: "new", helptexts: [
          language: "nl", helptext: "Dit is de primary key",
          language: "en", helptext: "This is the primary key"
        ], attributes: [], uses: 1,
        name: "SUBTITLE", status: "new", helptexts: [
          language: "nl", helptext: "Dit is de subtitel van een record",
          language: "en", helptext: "This is The subtitle of a record"
        ], attributes: [], uses: 1,
        name: "URL", status: "new", helptexts: [
          language: "nl", helptext: "De waarde wordt gezien als link.",
          language: "en", helptext: "The value becomes a link."
        ], attributes: [
          name: "link", status: "new", helptexts: [
            language: "nl", helptext: "De link krijgt deze waarde. Voorbeeld waarde is \"http://www.google.nl?search=[naam]\". de waarde van \"[naam]\" wordt ingevuld.",
            language: "en", helptext: "The link gets this value. Example value is \"http://www.google.nl?search=[name]\". the value of \"[name]\" gets filled in."
          ]
        ], uses: undefined,
        name: "TITLE", status: "new", helptexts: [
          language: "nl", helptext: "Dit is de hoofdtitel van een record",
          language: "en", helptext: "This is the maintitle of a record"
        ], attributes: [], uses: 1,
        name: "PHONE", status: "new", helptexts: [
          language: "nl", helptext: "De waarde wordt gezien als telefoonnummer",
          language: "en", helptext: "The value becomes a phonenumber"
        ], attributes: [], uses: undefined,
        name: "BUTTON", status: "new", helptexts: [
          language: "nl", helptext: "Uiterlijk van een knop",
          language: "en", helptext: "The value becomes a button"
        ], attributes: [], uses: undefined,
        name: "EMAIL", status: "new", helptexts: [
          language: "nl", helptext: "De waarde wordt gezien als e-mail adres",
          language: "en", helptext: "The value becomes a emailaddress"
        ], attributes: [], uses: undefined,
        name: "IMAGE", status: "new", helptexts: [
          language: "nl", helptext: "De waarde wordt als afbeelding weergegeven",
          language: "en", helptext: "The value gets displayed as image"
        ], attributes: [], uses: undefined,
        name: "html", status: "new", helptexts: [
          language: "nl", helptext: "De waarde wordt gezien als HTML",
          language: "en", helptext: "The value gets seen as custom HTML"
        ], attributes: [
          name: "HTML code", status: "new", helptexts: [
            language: "nl", helptext: "Vul hier je custom HTML code in. De waarde tussen de [] word vervangen door de data.",
            language: "en", helptext: "Enter your custom HTML here. The value between the [] will be replaced for the value."
          ]
        ], uses: undefined
      ]
    ;
    this.onDragEnd = this.onDragEnd.bind(this);
  
  onDragEnd(result: any) 
    // dropped outside the columns table
    if (!result.destination) 
      return;
    
    let newlist = [...this.state.fields.columns];
    newlist = reorder(
      newlist,
      result.source.index,
      result.destination.index
    );
    Object.keys(newlist).forEach((nr) => 
      newlist[parseInt(nr, 10)].index = parseInt(nr, 10);
    );
    this.setState( fields:  columns: newlist  );
    this.setState( canSave: true );
  
  async componentDidMount() 
    console.log("Start select columns");
    const fields = await HttpHelper.getJson<Fields>(`/$this.props.type/$this.props.id/columns`);
    this.setState(prevState => 
      return update(prevState, 
        fields:  $set: fields ,
        isLoading:  $set: false ,
      );
    );
    if (this.state.fields.columns == undefined) 
      this.setState( fields:  columns: []  );
    
    for (let i = 0; i < fields.columns.length; i++) 
      fields.columns[i].index = i;
    
    this.setState( fields:  columns: fields.columns  );
    const newlist = [...this.state.possibleTags];

    for (const column of fields.columns) 
      for (const tags of column.tags) 
        const index = newlist.map((item) => item.name).indexOf(tags.name);
        if (newlist[index].uses > 0) 
          newlist[index].uses = 0;
        
      
    
    this.setState( possibleTags: newlist );
  
  moveColumn(index: any, indexnr: any) 
    const cards = this.state.fields.columns;
    const sourceCard = cards.find((card: any) => card.index === index);
    const sortCards = cards.filter((card: any) => card.index !== index);
    sortCards.splice(indexnr, 0, sourceCard);
     Object.keys(sortCards).forEach((nr) => 
    sortCards[nr].index = parseInt(nr, 10);
    );
    this.setState( fields:  columns: sortCards  );
    this.setState( canSave: true );
  

  onDragStart = (e: any) => 
    e.dataTransfer.effectAllowed = "move";
    e.dataTransfer.setData("text/html", e.target.parentNode);
    e.dataTransfer.setDragImage(e.target.parentNode, 20, 20);
  
  ondragOver(e: any) 
    e.preventDefault();
  

  public render() 
    const columns = this.state.fields.columns || [] ;
    const  t  = this.props;
    let placeholder: any;

    if (columns.length < 1) 
      placeholder = <tr style=boxShadow: "0px 0px 0px 1px #8b8b8b", textAlign: "center" className="draggablerow"><td colSpan=6 >t("placeholder")</td></tr>;
    
    return (
      <form>
        <div className="App">
          <main>
            <button onClick=this.onSubmit className="btn btn-primary" type="submit" style=float: "right", boxShadow: "2px 2px 3px 1px" + shadowColor disabled=!this.state.canSave || this.state.isSaving>this.state.isSaving ? <i className="fa fa-spinner fa-spin"></i> : "" this.props.t("update")</button>
            <button onClick=this.onPreviewButtonClicked type="button" className="btn btn-primary"  style=float: "right", boxShadow: "2px 2px 3px 1px" + shadowColor, marginRight: "5px" >Preview</button><br/><br/>
            <DragDropContext onDragEnd=this.onDragEnd>
              <table className="col-8 table columns" style=tableLayout: "auto" >
                <thead className="" style=border: "2px solid #1b2847", background: "#1b2847", color: "white">
                  <tr>
                    <th colSpan=2 style=textAlign: "center">
                      <button onClick=this.onAddColumn disabled=columns.length > 14 ? true : false type="button" style=padding : "8px 16px", boxShadow: "2px 2px 3px 1px" + shadowColor  className="btn btn-primary btn-rounded"><i className="fa fa-plus"></i> </button>
                    </th>
                    <th>t("displayname")</th>
                    <th>Element</th>
                    <th>Tags</th>
                    <th></th>
                  </tr>
                </thead>
                <Droppable droppableId="droppable" direction="vertical">
                  (provided: any) => (
                    <tbody  ref=provided.innerRef>
                      Object.keys(columns).map((element, key) => (
                        <Draggable key="draggable" + key draggableId=element index=key>
                        (provided, snapshot) => (
                          <Column
                          key="column" + key
                          indexnr=key
                          toggleVisible=this.toggleVisible
                          onTextUpdate=this.onTextUpdate
                          onDeleteColumn=this.onDeleteColumn
                          onDeleteTag=this.onDeleteTag
                          onAddTag=this.onAddTag
                          possibleTags=this.state.possibleTags
                          onConfigButtonClicked=this.onConfigButtonClicked
                          onPreviewButtonClicked=this.onPreviewButtonClicked
                          onClosePreview=this.onClosePreview
                          provided=provided
                          snapshot=snapshot
                          language=this.props.language
                          ...columns[key]
                          />
                        )
                        </Draggable>
                      ))
                      provided.placeholder
                     </tbody>
                  )
                </Droppable>
              </table>
            </DragDropContext>
          </main>
        </div>

      </form>
    );
  

export default withNamespaces(["crmConnectorColumns", "Common"])(CrmConnectorColumns);

我希望有人能找出为什么当我在页面上向下滚动时我的可拖动对象会错位。

【问题讨论】:

你能在codesandbox中添加一个静态数据的例子吗?我尝试添加一个示例,但代码中缺少导入的文件。 我在这里创建了一个非常基本的示例,其中包含您的代码核心:codesandbox.io/s/my64446wl9 我没有看到与您相同的结果,所以我敢打赌它是在样式被覆盖某处。 浏览器版本不同?如果您得到相同的结果,也可以在隐身模式下尝试(有时扩展程序会干扰页面) 可能是您的可拖动元素上有一个溢出scoll。也许尝试设置overflow-y:隐藏在可拖动元素上 所以 onscroll 一些样式被覆盖了? 【参考方案1】:

也许答案为时已晚,但对于某些人来说,它可能会有所帮助。 如果您仔细观察,您会在滚动时看到偏移量,这就是样式损坏的原因。 对于解决方案,您应该考虑滚动容器,如果您将滚动附加到 HTMLElement,而不是 Window,则需要检查此example。 此问题与react-beautiful-dnd 本身有关,更新版本将解决该问题。

【讨论】:

【参考方案2】:

我遇到了同样的问题。对我来说,问题是 droppable(list) 在里面,例如,main 容器是可滚动的(即溢出:滚动)。

我通过将 droppable 转换为可滚动而不是 main container

解决了这个问题

有问题的示例

.main 
  background: #eee;
  padding: 3rem;
  height: 200px;
  overflow-y: scroll;

.droppable 
  padding: 1rem;
  background: #aaa;

.draggable 
  margin: 0.5rem 0;
  padding: 1rem;
  background: #ccc;
<div class="main">
  <div class="droppable">
    <div class="draggable">
        <span class="text"> item</span>
    </div>
      <div class="draggable">
          <span class="text"> item</span>
    </div>
      <div class="draggable">
        <span class="text"> item</span>
    </div>
  </div>
</div>

已解决问题的示例

.main 
  background: #eee;
  padding: 3rem;
  height: 200px;

.droppable 
  padding: 1rem;
  background: #aaa;
  height: 180px;
  overflow-y: scroll;

.draggable 
  margin: 0.5rem 0;
  padding: 1rem;
  background: #ccc;
<div class="main">
  <div class="droppable">
    <div class="draggable">
        <span class="text"> item</span>
    </div>
      <div class="draggable">
          <span class="text"> item</span>
    </div>
      <div class="draggable">
        <span class="text"> item</span>
    </div>
  </div>
</div>

仅在 CSS 中进行了更改,以使 droppable 容器比 main 容器更短,并将 overvlow-y:scroll 添加到 droppable

【讨论】:

【参考方案3】:

我也遇到了同样的问题。

我发现的唯一解决方法是

*在垂直拖动的情况下固定项目的高度。 *在水平拖动的情况下固定项目的宽度。

还有一件事要提到的是设置拖动到块的项目的显示属性。

【讨论】:

此处提供所需 CSS 的代码示例

以上是关于滚动后反应Dnd不在位置的主要内容,如果未能解决你的问题,请参考以下文章

不在 App.js 中时反应传单地图位置故障

渲染后将原生重置 ScrollView 反应到顶部

h5滚动屏幕之后,绝对定位位置不在页面最底部

React Dnd 实现横向和纵向拖拽排序

滚动视图位置中的 UIButton 意外

将内容附加到列表时保持滚动位置(AngularJS)