通过在 Dart 中拖动来移动元素

Moving elements by dragging in Dart

我正在尝试使用拖放来移动元素。我希望能够将元素拖动到不同的位置,当我放下它时,元素会移动到放置的位置。超级基本,没什么特别的。这是我目前所拥有的:

html:

<input type='button' id='drag' class='draggable' value='drag me' draggable='true'>

飞镖代码:

Element drag = querySelector('.draggable');
drag.onDragEnd.listen((MouseEvent e) {
  drag.style.left = '${e.client.x}px';
  drag.style.top = '${e.client.y}px';
});

这并不完全符合我的要求。该元素与我放置它的位置略有不同。我在 javascript 中看到了带有 appendChild、clone()、parentNode 的示例,但是我看到的 none 示例可以在 Dart 中重现。完成此任务的最佳方法是什么?我不想使用 DND 包,因为我真的想亲自尝试更好地理解这些概念。

index.html

<!doctype html>
<html>
  <head>
    <style>
      #dropzone {
        position: absolute;
        top: 50px;
        left: 50px;
        width: 300px;
        height: 150px;
        border: solid 1px lightgreen;
      }
      #dropzone.droptarget {
        background-color: lime;
      }
    </style>
  </head>
  <body>
    <input type='button' id='drag' class='draggable' value='drag me'
           draggable='true'>

    <div id="dropzone"></div>
    <script type="application/dart" src="index.dart"></script>
    <script src="packages/browser/dart.js"></script>
  </body>
</html>

index.dart

library _template.web;

import 'dart:html' as dom;
import 'dart:convert' show JSON;

main() async {
  dom.Element drag = dom.querySelector('.draggable');
  drag.onDragStart.listen((event) {
    final startPos = (event.target as dom.Element).getBoundingClientRect();
    final data = JSON.encode({
      'id': (event.target as dom.Element).id,
      'x': event.client.x - startPos.left,
      'y': event.client.y - startPos.top
    });
    event.dataTransfer.setData('text', data);
  });

  dom.Element dropTarget = dom.querySelector('#dropzone');
  dropTarget.onDragOver.listen((event) {
    event.preventDefault();
    dropTarget.classes.add('droptarget');
  });

  dropTarget.onDragLeave.listen((event) {
    event.preventDefault();
    dropTarget.classes.remove('droptarget');
  });

  dropTarget.onDrop.listen((event) {
    event.preventDefault();
    final data = JSON.decode(event.dataTransfer.getData('text'));
    final drag = dom.document.getElementById(data['id']);
    event.target.append(drag);
    drag.style
      ..position = 'absolute'
      ..left = '${event.offset.x - data['x']}px'
      ..top = '${event.offset.y - data['y']}px';
    dropTarget.classes.remove('droptarget');
  });
}

上面的答案是正确的,因此我不想编辑它。但是,我还想提供从上述内容中得出的另一个答案。与上述相比,它更加基础,因此更容易为初学者遵循基本概念。如下所述,我认为您不能移动元素,除非它们在可放置区域内。

index.html:

<!DOCTYPE html>
<html>
<head>
  <style>
  #dropzone {
    position: absolute;
    top: 100px;
    left: 50px;
    width: 300px;
    height: 150px;
    border: solid 1px;
    color: lightgreen;
  }</style>
</head>
<body>
  <div  id="dropzone">
    <input type='button' id='drag' class='draggable' value='drag me'
     draggable='true'>
  </div>
  <script type="application/dart" src="main.dart"></script>
</body>
</html>

main.dart:

import 'dart:html';
main() {
  Element drag = querySelector('.draggable');
  Element drop = querySelector('#dropzone');
  drag.onDragStart.listen((MouseEvent e) {
    var startPos = (e.target as Element).getBoundingClientRect();
    String xPos = "${e.client.x - startPos.left}";
    String yPos = "${e.client.y - startPos.top}";
    e.dataTransfer.setData('x', xPos);
    e.dataTransfer.setData('y', yPos);
  });
  drop.onDragOver.listen((MouseEvent e) {
    e.preventDefault();
  });
  drop.onDrop.listen((MouseEvent e) {
    e.stopPropagation();
    String xPos = e.dataTransfer.getData('x');
    String yPos = e.dataTransfer.getData('y');
    int x = num.parse(xPos);
    int y = num.parse(yPos);
    drag.style.position = 'absolute';
    drag.style
      ..left = '${e.offset.x - x}px'
      ..top = '${e.offset.y - y}px';
  });
}

我有同样的问题,由于上面的答案不能满足我的需求:

  1. 元素可自行拖动(无放置区)
  2. 可重复使用

对于基于包装器的解决方案,这个包可能是答案:https://pub.dartlang.org/packages/dnd

基于自定义元素的方法(当前光标样式不起作用):

  main(){
  document.registerElement('draggable-element',
    DraggableElement);
  querySelector('body').append(new DraggableElement()..text='draggable');
  }
  class DraggableElement extends HtmlElement with Draggability{
  DraggableElement.created():super.created(){
    learn_custom_draggability();
  }
  factory DraggableElement(){
    return new Element.tag('draggable-element');
  }
}
class Draggability{
  bool __custom_mouseDown = false;

  //The Coordinates of the mouse click
  //relative to the left top of the
  //element.
  Point<int> __custom_relative_mouse_position;
  void learn_custom_draggability(){
    if(this is! HtmlElement ){
      throw ("Draggability mixin "
          "is not compatible with"
          ' non-HtmlElement.');
    }
    var self = (this as HtmlElement);
    self.onMouseDown.listen(mouseDownEventHandler);
    self.onMouseUp.listen(mouseUpEventHandler);
    //styling
    self.style.position = 'absolute';

    window.onMouseMove
        .listen(mouseMoveEventHandler);
  }

  void mouseMoveEventHandler(MouseEvent e){
    if(!__custom_mouseDown) return;
    int xoffset = __custom_relative_mouse_position.x,
      yoffset = __custom_relative_mouse_position.y;

    var self = (this as HtmlElement);
    int x = e.client.x-xoffset,
        y = e.client.y-yoffset;
    print(x);
    if(y == 0) return;
    self.style
      ..top = y.toString() +'px'
      ..left = x.toString()+'px';
  }

  void mouseDownEventHandler(MouseEvent e){
    print('mouse down');
    __custom_mouseDown = true;
    var self = (this as HtmlElement);
    self.style.cursor = 'grabbing';

    __custom_relative_mouse_position =
      e.offset;
  }

  void mouseUpEventHandler(MouseEvent e){
    print('mouse up');
    __custom_mouseDown = false;
    var self = (this as HtmlElement);
    self.style.cursor = 'default';
  }
}

编辑:

是的,谢谢 Günter Zöchbauer 告诉我有关可反射的信息。它非常小,编译速度很快。
有点偏离主题但发帖是因为 mixins 和下面的模式齐头并进。

import 'package:reflectable/reflectable.dart';
class Reflector extends Reflectable{
  const Reflector():
        super(
          instanceInvokeCapability,
          declarationsCapability
      );
}
const reflector = const Reflector();
@reflector
class CustomBase extends HtmlElement{
  CustomBase.created():super.created(){
    learn();
  }
  learn(){
    InstanceMirror selfMirror = reflector.reflect(this);
    var methods = selfMirror.type.instanceMembers;
    RegExp pattern = new RegExp('^learn_custom_.*bility$');
    for(var k in methods.keys){
      if(pattern.firstMatch(k.toString()) != null){
        selfMirror.invoke(k,[]);
      }
    }
  }
}

Include: "reflectable: ^0.5.0" under dependencies 和 "- reflectable: entry_points: web/index.dart" etc under transformers 在 pubspec.yaml 中并像上面那样扩展自定义 class 而不是 HtmlElement 并且 selfMirror.invoke 神奇地调用您的初始化程序,只要它们的名称与给定模式匹配。当你的 class 有相当多的能力时很有用。