JS/CSS: Wie rotiere ich ein Element 360 um einen bestimmten Punkt?

Kokujou

Lieutenant
Registriert
Dez. 2017
Beiträge
948
Hey Leute!

ich hab hier mal wieder ein kniffliges Problem... eigentlich sollte ein bischen Mathe dafür völlig ausreichen aber die Werte die ich bekomme sind eigenartig und unerwartet, darum muss ich um Hilfe bitten.

Szenario: Ich habe einen Container und in dem sind x-Elemente. Diese Elemente sollten um den Mittelpunkt des Containers rotiert werden entsprechend ihres Index im Winkel von 0-360°.

mein code:
Code:
items.forEach((item, index) => {
            var targetAngle = (360.0 / items.length) * (index + this.rotationOffset);
            var container = item.parentElement;
            var targetX = Math.cos(targetAngle) * (-container.offsetWidth / 3.0);
            var targetY = Math.sin(targetAngle) * (container.offsetWidth / 3.0);
            item.style.transform = `translate(${targetX}px, ${targetY}px)`;
        });

statt dass sie sich ordentlich nacheinander im Kreis anordnen ist das ergebnis eher so:
1624450178845.png

und an statt dass ein item nach dem nächsten kommt sind sie bunt durchgemischt. ich hoffe jemand hat eine Idee :S
 
  • Gefällt mir
Reaktionen: Kokujou
okay, den hab ich jetzt nicht kommen sehen... das hatte ich jetzt so gar nicht auf der Rechnung >.< sorry! und danke für den schnellen Fix!
 
Du könntest es auch etwas einfacher machen wenn du die Flaggen jeweils in einzelne child elements mit height = 100% packst. Wenn du den Winkel festgestellt hast drehst du das jeweilige child element mit transform: rotate(60deg); transform-origin: center; und die Flagge selber mit -60deg damit sie gerade steht. Damit kannst du auch leicht eine CSS Transition dazunehmen.
 
  • Gefällt mir
Reaktionen: kim88 und netzgestaltung
Merke dir das generell, wenn es um Winkel geht und es passieren unerwartete seltsame Dinge -> Doku lesen, Code checken und/oder mit Radianten versuchen. Kann nicht sagen wie oft ich da schon drüber gestolpert bin...:D
 
  • Gefällt mir
Reaktionen: Kokujou
floq0r schrieb:
Du könntest es auch etwas einfacher machen wenn du die Flaggen jeweils in einzelne child elements mit height = 100% packst. Wenn du den Winkel festgestellt hast drehst du das jeweilige child element mit transform: rotate(60deg); transform-origin: center; und die Flagge selber mit -60deg damit sie gerade steht. Damit kannst du auch leicht eine CSS Transition dazunehmen.
tatsächlich hatte ich auch erst transform-origin und translate mit rotate kombiniert. aber so einfach ist es nicht denn hier geht es ja um flaggen und die bilder selbst dürfen nicht rotiert werden, sonst kommt man durcheinander.
 
Deshalb ja:
floq0r schrieb:
Du könntest es auch etwas einfacher machen wenn du die Flaggen jeweils in einzelne child elements mit height = 100% packst. Wenn du den Winkel festgestellt hast drehst du das jeweilige child element mit transform: rotate(60deg); transform-origin: center; und die Flagge selber mit -60deg damit sie gerade steht. Damit kannst du auch leicht eine CSS Transition dazunehmen.
:)
 
  • Gefällt mir
Reaktionen: savuti
Memo an mich: besser lesen >.< ist ja schlimm heute! Manchmal hab ich echt ein Brett vorm Kopf...
 
  • Gefällt mir
Reaktionen: savuti
Passiert, mach dich nicht verrückt ;)
 
  • Gefällt mir
Reaktionen: floq0r und savuti
leider funktioniert es immer noch nicht :( CSS scheint nicht klug genug zu sein um von 360° zu 0° schnell zu bewegen. stattdessen dreht sich das Rad erstmal komplett rum :(

hat jemand ne Lösung dafür?
 
Ich weiß nicht wie und ob CSS es kann, aber dreh doch einfach weiter als 359° falls dieser Übergang ein Problem ist? Es ist nicht nur erlaubt zwischen 0° und 359° die Winkel anzugeben.
 
das problem ist dass er diese werte dann im endresultat des CSS wieder umwandelt.
also wenn ich sage rotiere auf -32° macht er daraus 328°

ich müsste also die tatscähliche aktuelle CSS position kennen und die einzige möglichkeit dafür wäre den style.transform string zu lesen und zu parsen.. sehr unsexy. vor allem da er ja so aussieht:

Code:
rotate(${targetAngle}deg) translateY(-${container.offsetHeight / 3}px) rotate(${-targetAngle}deg)
 
Ich bin da schon manchmal den Umweg gegangen den entsprechenden Wert zusätzlich in ein data-attribute zu speichern, dann musst du nicht mit den Werten abmühen die beim Auslesen von CSS values daherkommen. Unter Umständen kommt da nämlich auch ein matrix3d()
 
ich könnte den wert natürlich abspeichern, das problem ist aber dass die CSS den Wert verändern. und da hilft alles speichern leider nichts :(
aber gut wenn es keinen anderen weg gibt muss ich wohl parsen...
 
Was ist mit einer Sphere die du drehst und setzt dann die Flaggen ein. Die Sphere kann du auf eine Perspektive festnageln, die Flaggen verlinken. Das gibt es schon fertig.
 
weniger Animationen als Transitionen, ist ja ein unterschied. Auch bei einer sphäre wäre es vermutlich dasselbe. das problem ist halt das überdrehen. denn es gibt ja nur 0-360° intern.

also wenn du bei 360° bist und auf 361° drehen willst wäre das umgerechnet wieder 1° und die sphäre würde statt ein bischen vorwärts einfach den ganzen weg zurück drehen. und das ist halt... meh!
 
Das stimmt allerdings... Da könnte man nur am Ender der Umdrehung eine Klasse setzen, die die Transition deaktiviert, dann die Rotation auf 0°C zurücksetzen, Klasse entfernen blabla wobei es hier immer zu Timing-Problemen kommt, dann muss man wieder mit setTimeout() herumtun. Da wäre es am besten du machst es doch über JS.
 
okay ich glaube ich habs jetzt... ich bin nicht sicher warum es plötzlich funktioniert, aber folgender Code funktioniert:

Code:
 updated(_changedProperties) {
        super.updated(_changedProperties);
        if (!_changedProperties.has('rotationOffset')) return;
        this.animationRunning = true;
        /** @type {NodeListOf<HTMLElement>} */ var languageIcons = this.shadowRoot.querySelectorAll('.language-selector-icon');
        /** @type {HTMLElement} */ var container;
        languageIcons.forEach((item, index) => {
            var anglePartition = 360.0 / languageIcons.length;
            var targetAngle = anglePartition * index + (360.0 / languageIcons.length) * this.rotationOffset;
            container = item.parentElement;
            item.style.transform = `rotate(${targetAngle}deg) translateY(-${container.offsetHeight / 3}px) rotate(${-targetAngle}deg)`;
        });
        setTimeout(() => {
            this.animationRunning = false;
        }, 500);
}

vorher hab ich das rotationOffset geclamped, aber genau da lag der Fehler. Wenn man es für links einfach dekrementiert und für rechts inkrementiert und immer größer werden lässt entsteht eine erfolgreiche Drehung. Jetzt gab es aber noch ein weiteres Hindernis: Wie springe ich zu einem bestimmten Item? Und das war leider etwas eklatant und zwecks Lesbarkeit hab ich es dann so gemacht:

Code:
    gotoItem(index) {
        if (oldIndex == index) return;
        var clampedTargetOffset = nations.length - index;
        if (index == 0) clampedTargetOffset = 0;
        var clampedRotationOffset = this.rotationOffset % nations.length;
        var backSteps = 0;
        while (true) {
            backSteps--;
            var target = clampedRotationOffset + backSteps;
            if (target < 0) target += nations.length;
            if (target < 0) break;
            if (target === clampedTargetOffset) break;
        }
        backSteps *= -1;
        var foreSteps = 0;
        while (true) {
            foreSteps++;
            var target = clampedRotationOffset + foreSteps;
            if (target >= nations.length) target -= nations.length;
            if (target >= nations.length) break;
            if (target == clampedTargetOffset) break;
        }
        if (foreSteps < backSteps) this.rotationOffset += foreSteps;
        else this.rotationOffset -= backSteps;
    }
ne for-schleife hätte es hier vermutlich auch getan, aber eigenltich ist die abbruchbedingung nur zur sicherheit weil der algorithmus schon 8mal meinen browser hat abstürzen lassen, ^^

ich finde es etwas sehr brute-force aber besser als nichts.
 
Zurück
Oben