Drag and Drop

Uzi

Mitglied
Moin Zusammen,
ich versuche da ne kleines Spielchen hin zu bekommen aber weiß nicht genau wie da ran gehen soll, bin da über jeden Tipp dankbar.

Die Grundidee sieht folgend aus:
Man hat 2 texte sagen wir "A" und "B" die links irgendwo stehen und rechts hat man 2 Vierecke. Man kann nun die 2 Texte in die entsprechenden Vierecke ziehen und unten wäre dann ein Button "check" oder sowas... und erst wenn man auf den Button drückt, wird geprüft ob die Texte in den Richtigen Blöcken sind. Sollte das der Fall sein dann leuchtet das richtige Feld grün auf und das Falsche Feld Rot.

Ich hab mir das überlegt das ich es anhand von Drag and Drop mache und dann mit einem art "event" das irgendwie prüfen lasse. Aber wie? Ich hab viele Events gefunden die so ähnlich funktionieren wie z.b. farbe wechseln wenn in einem bestimmten DIV was drinnen ist usw. aber nicht wo erst nach Betätigung eines Buttons.
 
Drag & Drop gehört zum HTML5 Standard. Wie das grundlegen funktioniert, wird hier beschrieben.

Ich habe ein Beispiel erstellt, an dem du sehen kannst wie das in etwa aussehen könnte. Wenn du Fragen dazu hast darfst du sie natürlich gerne stellen.

Zuerst ein wenig HTML. 4 div-Container welche mit Zahlen von 1-4 bezeichnet sind. Diese sollen in 2 weitere div-Container sortiert werden und zwar nach geraden und ungeraden Zahlen. Wichtig ist das draggable-Attribut.

HTML:
<div class="dragContainer" draggable="true" id="drag1">1</div>
<div class="dragContainer" draggable="true" id="drag2">2</div>
<div class="dragContainer" draggable="true" id="drag3">3</div>
<div class="dragContainer" draggable="true" id="drag4">4</div>

<div class="targetContainer" id="target1"></div>
<div class="targetContainer" id="target2"></div>
Dazu simpler CSS-Code um die Funktionalität zu verdeutlichen:

CSS:
.targetContainer {
    float: left;
    width: 200px;
    min-height: 100px;
    border: 1px solid #555;
}

#target1 {
    background-color: #ddffdd;
}

#target2 {
    background-color: #ffdddd;
}

.dragContainer {
    position: relative;
    width: 180px;
    height: 100px;
    background: #333;
    color: #eee;
    text-align: center;
    line-height: 50px;
    cursor: pointer;
}

.dragContainer:hover {
    background-color: #222;
}
Der Rest ist Javascript. Zuerst braucht es die Möglichkeit die Elemente überhaupt zu bewegen. Im dragstart-Event wird mittels dataTransfer.setData mitgeteilt, was genau verschoben werden soll. In dem Fall this repräsentativ für das aktuell bewegte Element. Im drop-Event wird das bewegte Element dann mittels appendChild in das Zielelement eingefügt. Wichtig ist auch, dass sowohl im dragover- als auch im drop-Event preventDefault aufgerufen wird, ansonsten kommt es unter Anderem zum Neuladen der Seite.

Javascript:
var dragContainers = document.getElementsByClassName("dragContainer");
var currentDragElement = null;

for(var i = 0; i < dragContainers.length; i++)
{
    dragContainers[i].addEventListener("dragstart", function(event){
        event.dataTransfer.setData("text", this);
        currentDragElement = this;
     });
}

var targetContainers = document.getElementsByClassName("targetContainer");

for(var i = 0; i < targetContainers.length; i++)
{
    targetContainers[i].addEventListener("dragover", function(event){
        event.preventDefault();
    });
    targetContainers[i].addEventListener("drop", function(event){
        event.preventDefault();
        this.appendChild(currentDragElement);
    });
}
Zuletzt braucht es dann noch eine Funktion zum prüfen der Zusammenhänge, das könnte dann so aussehen:

Javascript:
var relations =
[
    // "target1" muss enthalten: "drag2" und "drag4" (beliebig erweiterbar).
    ["target1", ["drag2", "drag4"]],
    ["target2", ["drag1", "drag3"]]
];

function checkResult()
{
    for(var i = 0; i < relations.length; i++)
    {
        var target = document.getElementById(relations[i][0]);
        var drags = target.getElementsByClassName("dragContainer");

        if(drags.length != relations[i][1].length)
        {
            return false;
        }

        var dragIds = new Array();

        for(var j = 0; j < drags.length; j++)
        {
            dragIds.push(drags[j].id);
            if(relations[i][1].indexOf(drags[j].id) < 0)
            {
                return false;
            }
        }
    }

    return true;
}

Im drop-Event muss diese Funktion aufgerufen werden. Wenn alle Elemente korrekt platziert sind wird true zurückgegeben, ansonsten false.
 
Klappt perfekt, danke dir.
Ich hab es nur um eine function erweitert und nem Button so das "checkResult" erst ausgelöst wird wenn ich den Butten betätige und mir dann das Ergebnis mitteilt.

Ich hätte noch ne Frage, würde gerne eine mehrstufige Pyramide bauen mittels HTML und jede zwischen stufe soll ein Eigener Div seine (siehe anhang).
Ist dies Mögliche das verschiedene Divs so perfekt ineinander passen und wie macht man das? An CSS rum zu basteln bis man mit glück das erreicht hat wird schwer :/
 

Anhänge

  • DreiEck.jpg
    DreiEck.jpg
    23 KB · Aufrufe: 1
Ich hätte noch ne Frage, würde gerne eine mehrstufige Pyramide bauen mittels HTML und jede zwischen stufe soll ein Eigener Div seine (siehe anhang).
Ist dies Mögliche das verschiedene Divs so perfekt ineinander passen und wie macht man das? An CSS rum zu basteln bis man mit glück das erreicht hat wird schwer :/
Nicht wirklich :D:cool:

Demos:
https://codepen.io/nikolaygit/pen/wnchq
https://jsfiddle.net/augburto/Zb9nb/

Tutorial:
https://davidwalsh.name/css-triangles

Online-Generator:
http://apps.eky.hk/css-triangle-generator/
 
  • Gefällt mir
Reaktionen: Uzi
Da du ja eine Art Spiel zu entwickeln scheinst und sowieso schon Javascript benutzt, kannst du deine Pyramide auch mit Canvas zeichnen.

Das ist fürs Erste etwas aufwändiger, gibt dir aber sehr viel mehr Kontrolle über das, was du später darstellst.

Am Anfang wäre da ein wenig Geometrie. HTML Container sind von Natur aus Rechtecke. Aus einem Rechteck lassen sich jedoch sehr leicht Punkte für ein Dreieck und später die Trapez-Teilstücke errechnen.

triangleConstruction.png
Code:
Allgemein:

a = Breite des Rechtecks
b = Höhe des Rechtecks
c = a / 2
d = b / Anzahl Trapeze
e = c / Anzahl Trapeze

Im Beispiel:

a           = 600
b           = 500
c = 600 / 2 = 300
d = 500 / 4 = 125
e = 300 / 4 = 75
Es ist leicht zu erkennen, dass die Stufen immer von gleicher Größe sind, also lässt sich das in einer Schleife mit einem Multiplikator für die Verschiebung realisieren.

Am Anfang steht HTML. Es bedarf eines Wrapper-Elementes und pro gewünschtem Trapez ein Kindelement:
HTML:
<div class="pyramid" id="testPyramid">
    <div></div>
    <div></div>
    <div></div>
    <div></div>
</div>
Es handelt sich zwar mathematisch gesehen nicht um eine Pyramide, die Bezeichnung finde ich aber einleuchtend für das Vorhaben.

Damit das später hinzugefügte Canvas Element noch von Text überdeckt werden kann ohne sich zu verschieben, ist etwas CSS nötig. Außerdem kann über die ID bereits die gewünschte Größe eingegeben werden. Ich war noch nie CSS-Experte, bitte nicht schimpfen sondern konstruktiv anmerken falls ich das unsauber löse:
CSS:
#testPyramid {
    width: 600px;
    height: 500px;
}

.pyramid div {
    position: relative;
}

.pyramid div canvas {
    position: absolute;
    top: 0;
    left: 0;
}

Als nächstes sollte eine Javascript Funktion angelegt werden, welche die Pyramide generiert. Als Übergabewert bietet sich das entsprechende HTML Element als DOM-Node an.
Code:
function drawPyramid(domElement)
{

}

var pyramid = document.getElementById("testPyramid");

drawPyramid(pyramid);

Jetzt müssen ein paar Daten erhoben werden. Und zwar die Abmessungen, welche ich eben beschrieben habe. Der Übersicht halber lohnt es sich hier ein Objekt anzulegen. Das erleichtert den Zugriff später ungemein. Besonders bei der Arbeit mit Canvas kommen schnell unzählige Variablennamen auf einem Haufen zusammen. Darum ist es immer hilfreich dem eigenen Auge etwas auf die Sprünge zu helfen.

Ein Javascript Objekt anlegen ist einfach. Jede Funktion ist ein Objekt und man kann mit dem new-Keyword eine Instanz erstellen. Innerhalb der Funktion kann this.variablenname verwendet werden, um später nameMeinerInstanz.variablenname aufrufen zu können (Stichwort Membervariable):
Code:
function drawPyramid(domElement)
{
    var Pyramid = function(domElement)
    {
        this.width = domElement.offsetWidth;
        this.height = domElement.offsetHeight;
        this.trapezoidNodes = domElement.getElementsByTagName("div");
        this.trapezoidHeight = Math.floor(this.height / this.trapezoidNodes.length);
        this.step = this.width / this.trapezoidNodes.length / 2;
    }

    var pyramid = new Pyramid(domElement);
}

In width und height finden sich a und b, trapezoidNodes beinhaltet eine HTML-Collection mit den Kind-DIV-Elementen, trapezoidHeight ist d, step ist e. Die Variable c befindet sich leicht versteckt am Ende von Zeile 9, wo durch 2 geteilt wird.

Jetzt kommt der Zeitpunkt, an dem trapezoidNodes durchlaufen werden muss. In jedes dieser Elemente wird ein Trapez kalkulierter Größe gezeichnet.

Code:
function drawPyramid(domElement)
{
    var Pyramid = function(domElement){ /* ... */ }

    var pyramid = new Pyramid(domElement);

    for(var i = 0; i < pyramid.trapezoidNodes.length; i++)
    {
        
    }
}

Zuerst muss ein neues Canvas-Element angelegt werden. Diesem müssen Breite und Höhe des aktuellen trapezoidNode-Containers zugewiesen werden. Außerdem erhält der Container ebenso Höhe und Breite, jedoch nicht als HTML-Attribut, sondern als CSS-Eigenschaft:
Code:
for(var i = 0; i < pyramid.trapezoidNodes.length; i++)
{
    var canvas = document.createElement("canvas");
    canvas.setAttribute("width", pyramid.width);
    canvas.setAttribute("height", pyramid.trapezoidHeight);

    pyramid.trapezoidNodes[i].style.width = pyramid.width + "px";      
    pyramid.trapezoidNodes[i].style.height = pyramid.trapezoidHeight + "px";   

    pyramid.trapezoidNodes[i].appendChild(canvas);
}

Jetzt wird es ein letztes mal kompliziert. Es muss für das aktuelle Element der Seitenabstand für die obere und untere Kante des Trapezes berechnet werden. Bei jedem Element vergrößert sich der Abstand gleichbleibend, es reicht also theoretisch mit der Zählvariable der Schleife zu multiplizieren. Dafür müsste lediglich von unten angefangen werden, weil der Abstand dort am geringsten ist und mit einer dementsprechend kleineren Zahl multipliziert werden müsste.

Leider handelt es sich bei der trapezoidNodes-Variable um eine HTML-Collection und kein Array. Eine HTML-Collection kann man nicht einfach rückwärts durchlaufen, da es sich um eine Referenz auf die Elemente im HTML-Stammbaum handelt. Man müsste die Elemente also kopieren, die Originale löschen, die Kopien neu sortieren, das Canvas zeichnen und jede Kopie neu in den Stammbaum einsortieren. Das klingt nach Arbeit für uns und besonders die Ressourcen des Clients, lassen wir also.

Statt der ganzen Schleife, wird nur der Multiplikator umgedreht:
Code:
var margin = {
    topLine: pyramid.step * (pyramid.trapezoidNodes.length - i),
    bottomLine: pyramid.step * (pyramid.trapezoidNodes.length - i) - pyramid.step
}

Der untere Rand ist natürlich zu jeder Seite je ein mal step länger als der obere. Nun kann gezeichnet werden. Da nur gerade Linien verwendet werden halb so wild. Den Kontext des Canvas-Elementes heranziehen und mit beginPath, moveTo, lineTo und stroke ein wenig malen.
Code:
var ctx = canvas.getContext("2d");  

ctx.beginPath();
       
ctx.moveTo(margin.bottomLine, pyramid.trapezoidHeight);
ctx.lineTo(margin.topLine, 0);
ctx.lineTo(pyramid.width - margin.topLine, 0);
ctx.lineTo(pyramid.width - margin.bottomLine, pyramid.trapezoidHeight);

ctx.stroke();

Jede moveTo- bzw. lineTo-Funktion repräsentiert einen Punkt in der Reihenfolge: unten links -> oben links -> oben rechts -> unten rechts
Es fällt auf, dass weder im Script noch im Ergebnis der untere Rand gezeichnet wird. Das hat den Grund, dass der obere Rand des unteren Trapezes sich mit dem unteren Rand des oberen Trapezes überschneidet. Wird an dieser Stelle doppelt gezeichnet, wird die Linie dicker. Darum wird nur im letzten Element ein unterer Rand gezeichnet. Dazu reicht es folgendes anzuhängen:
Code:
if(i != pyramid.trapezoidNodes.length - 1)
{
    ctx.stroke();
}
else
{
    ctx.lineTo(margin.bottomLine, pyramid.trapezoidHeight);
    ctx.stroke();
}

Mit dieser Basis lässt sich jetzt viel anfangen. Zum Einen kann man mehr div-Container für mehr Trapeze anlegen oder mit den Maßen der Pyramide spielen, zum Anderen kann man noch weitere Funktionalität schaffen. Beispielsweise könnte man optionale Parameter einfügen anhand derer entschieden wird ob ausgefüllt (fill) oder ein Rahmen gezogen (stroke) oder beides wird. Oder über das dataSet ermöglichen Containern verschiedene Hintergrundfarben zuzuweisen.

Ich lasse mal ein Beispiel für die erweiterten Funktionen zum anschauen da:
HTML:
<div class="pyramid" id="testPyramid">
    <div data-background-color="#a8d500"></div>
    <div data-background-color="#7cbe04"></div>
    <div data-background-color="#6bb500"></div>
    <div data-background-color="#429d00"></div>
</div>
<div class="pyramid" id="testPyramid2">
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
</div>
CSS:
#testPyramid {
    width: 600px;
    height: 500px;
}

#testPyramid2 {
    width: 400px;
    height: 400px;
}

.pyramid div {
    position: relative;
}

.pyramid div canvas {
    position: absolute;
    top: 0;
    left: 0;
}
Code:
var pyramid = document.getElementById("testPyramid");
var pyramid2 = document.getElementById("testPyramid2");

drawPyramid(pyramid);
drawPyramid(pyramid2, false, true);

function drawPyramid(domElement, fill = true, stroke = false)
{
    var Pyramid = function(domElement)
    {
        this.width = domElement.offsetWidth;
        this.height = domElement.offsetHeight;
        this.trapezoidNodes = domElement.getElementsByTagName("div");
        this.trapezoidHeight = Math.floor(this.height / this.trapezoidNodes.length);
        this.step = this.width / this.trapezoidNodes.length / 2;
    }

    var pyramid = new Pyramid(domElement);

    for(var i = 0; i < pyramid.trapezoidNodes.length; i++)
    {
        var canvas = document.createElement("canvas");
        canvas.setAttribute("width", pyramid.width);
        canvas.setAttribute("height", pyramid.trapezoidHeight);

        pyramid.trapezoidNodes[i].style.width = pyramid.width + "px";      
        pyramid.trapezoidNodes[i].style.height = pyramid.trapezoidHeight + "px";   

        pyramid.trapezoidNodes[i].appendChild(canvas);

        var margin = {
            topLine: pyramid.step * (pyramid.trapezoidNodes.length - i),
            bottomLine: pyramid.step * (pyramid.trapezoidNodes.length - i) - pyramid.step
        }

        var ctx = canvas.getContext("2d");  
        ctx.fillStyle = pyramid.trapezoidNodes[i].dataset.backgroundColor;

        ctx.beginPath();
       
        ctx.moveTo(margin.bottomLine, pyramid.trapezoidHeight);
        ctx.lineTo(margin.topLine, 0);
        ctx.lineTo(pyramid.width - margin.topLine, 0);
        ctx.lineTo(pyramid.width - margin.bottomLine, pyramid.trapezoidHeight);

        if(fill)
        {
            ctx.fill();
        }

        if(stroke)
        {
            if(i != pyramid.trapezoidNodes.length - 1)
            {
                ctx.stroke();
            }
            else
            {
                ctx.lineTo(margin.bottomLine, pyramid.trapezoidHeight);
                ctx.stroke();
            }
        }  
    }
}

Natürlich lässt sich das Drag & Drop Prinzip auch hier anwenden, du kannst ja mal versuchen es umzusetzen, 1-2 Sachen können da potentiell schieflaufen aber ich bin sicher du schaffst das. Ansonsten sind wir ja auch weiterhin hier zum helfen.
 
Ich danke dir vielmals für die Super Erklärung und natürlich für die zeit für die ganzen Beispiele. Ich mach mich sofort ran an die Arbeit
 
Hätte ne CSS-Frage :D
meine Pyramide sieht folgend aus: (siehe anhang)
Wenn ich die Blöcke jetzt von Links auf die Pyramiden ebenen Ablege, dann will ich das wenn ich mehrere Blöcke in eine Ebene ablege, sich die Blöcke seitwärts Stapeln und nicht nach unten.
Könnte Jemand da auf die Sprünge helfen?
 

Anhänge

  • Screenshot_1.png
    Screenshot_1.png
    8,6 KB · Aufrufe: 4
Ich würde überprüfen, ob sich in einem Teil der Pyramide mehrere Elemente befinden und dementsprechend die Größe eben dieser justieren sowie mittels float seitlich anordnen.

Wenn du eine präzisere Erklärung brauchst, dann müsstest du mal deinen Code zeigen. Das heißt auch das, was du eventuell mit Javascript generiert hast. Das findest du in der Entwicklerkonsole. Diese kannst du in Firefox, Chrome und Edge mittels F12 aufrufen, bei anderen Browsern weiß ich es leider nicht genau. Dort hast du dann so etwas wie den Inspektor oder DOM-Explorer, wo du den aktuellen HTML Code einsehen kannst. Da kannst du dann je nach Browser z. B. mittels Rechtsklick auf das html-Element den kompletten Code kopieren. In Firefox kannst du das Element auch mit linksklick markieren und das Tastaturkürzel zum kopieren nutzen.
 
Ich hab mal ein JS-fiddle erstellt:
https://jsfiddle.net/6dkL7e7e/

zwei Sachen bekomme ich nicht hin.
erstens: Ich würde gerne, dass wenn mehrere Blöcke in einer Spalter der Pyramide sind, die sich seitlich stapeln und nicht untereinander...
und zweitens: Das ergebnis "Richtig oder Falsch" soll sofort gecheckt und ausgegeben werden und nicht.... also für jedes Element einzeln und nicht erst wenn alle 5 Blöcke von 1-5 richtig sortiert sind wird "Richtig" angegeben
 
Zurück