Scrollytelling: Geschichte eines Unternehmens

Beitragsbild Scrollytelling - Geschichte eines Unternehmens

Autorinnen: Maria Sael  & Jeanice Noraman 


Inhalt

Einleitung

Jedes Unternehmen hat eine Geschichte, diese wird häufig auf der Unternehmenswebsite dargestellt. Doch wie kann man sich als Unternehmen von anderen abheben und die (potenziellen) Kunden und User ansprechen?

Eine Möglichkeit, um das zu erreichen ist das sogenannte “Scrollytelling”, bei dem Storytelling auf eine interaktive und kreative Ebene gehoben wird. Dem User wird mithilfe von unterschiedlichen Scroll-Techniken eine Geschichte auf eine neue und interessante Art übermittelt.

Einige Beispiele von Scrollytelling auf hohem Niveau:

Als Hilfestellung und Inspiration hat uns das Youtube-Video “Apple Airpod Pro Javascript Animation Tutorial” von Dev Ed gedient.

Konzept

Um die „Über uns“-Seite des Unternehmens „Cleantaxx“ spannender und interaktiver zu gestalten, kann der User durch die Implementierung von Scrollytelling die Seite selbst steuern. Durch Scrollen wird die Geschichte des Unternehmens erzählt, so dass der User auf die Reise in die Geschichte von Cleantaxx mitgenommen wird und sich ein Bild über das Unternehmen machen kann.

Das Ziel ist es, die Informationen auf interaktive Art zu vermitteln und den User zu ermutigen weiter zu scrollen und ihn zu animieren bis zum Ende dranzubleiben. Dabei sollen die Informationen weiterhin auf eine seriöse Art übermittelt werden.

Aufbau

Der User wird mit einem Intro-Video begrüßt, das er selbst durch Scrollen steuert. Beim Runterscrollen wird das Video abgespielt und durch Hochscrollen kann es wieder zurückgespult werden. Danach erhält der User mit einem kurzen Text die wesentlichen Informationen über das Unternehmen.

Scrollytelling Bild 1
Header und Intro

Anschließend beginnt die Unternehmensgeschichte, wobei man auch diese selbst steuert. Beginnend mit dem Jahr 2009 kann der User durch Scrollen die wichtigsten Ereignisse des Unternehmens erscheinen lassen. Auf der Reise durch die Unternehmensgeschichte wird der User von einem grünen Punkt auf der Timeline begleitet. Die blauen Punkte zeigen die wichtigsten Ereignisse im Verlauf der Jahre an, die erscheinen wenn man runterscrollt.

Scrollytelling Bild 2
Item auf der Timeline

Code

HTML

Da wir uns auf die Animation konzentrieren wollen und uns nicht mit dem Aufbau einer gesamten Website beschäftigen wollen ist der Header nur beispielhaft als Screenshot im Code eingefügt.

Der Content befindet sich innerhalb des Bodys in div-containern, angefangen mit dem Intro-Video und einem Text in der Mitte des Videos (welcher mithilfe von JavaScript durch scrollen verschwindet).

<!--Beispielhafter Header-->
  <header>
    <div class="container">
      <img src="Medien/header.png">
    </div>
  </header>

  <body>
    <!--Intro mit Video-->
    <div class="intro">
      <h1>Wer wir sind</h1>
      <video src="Medien/cleantaxxIntro.mp4" type="video/mp4"></video>
    </div>

Nach dem Video folgt ein Block mit einer kurzen Einführung zum Unternehmen und anschließend ist die Timeline platziert. Diese enthält in div-containern mit der Klasse “timeline-item” die Boxen links und rechts vom Zeitstrahl. Auf der Timeline haben wir für jedes timeline-item ein Timeline-Icon erstellt, welches ein wichtiges Ereignis darstellt. Im div “timelineContent” ist für jedes Ereignis die Jahreszahl, ein Bild und ein Text angegeben. Mithilfe der id’s “content1” etc. identifizieren wir im JS-Teil die Boxen, um sie pinnen zu können. Die fadeIn Klassen sind ebenfalls für den JS-Teil wichtig, damit wir die verschiedenen Elemente nacheinander erscheinen lassen können.

<!--Info übers Unternehmen-->
    <div class="container">
      <div id="info">
        <h1>Unternehmen</h1>
        <h2> Cleantaxx gehört in der Branche zu den Unternehmen mit der meisten Erfahrung im Bereich der Rußfilterreinigung. Jeden Tag liefern wir eine umweltschonende Alternative zum Neukauf. Trotz der anhaltenden positiven Entwicklung mit dem stetigen Wachstum sind wir ein flexibles, wegweisendes Unternehmen mit flachen Hierarchien geblieben. </h2>
      </div>

      <div id="timeline">
        <!--Box für Jahr 2009-->
        <div class="timelineItem">
          <div class="timelineIcon2"></div>
          <div class="timelineIcon"></div>
          <div class="timelineContent" id="content1">
            <h2>2009</h2>
            <img class="fadeIn" src="https://i.ibb.co/5xRmdw2/benjamin-kleemann-cleantaxx-geschaeftsfuehrer.jpg">
            <p class="fadeIn">Benjamin Kleemann gründete im Jahr 2009 nach einem Jahr Marktforschung das Unternehmen Cleantaxx. Geschaffen wurde damit ein unabhängiger Dienstleister für die Reinigung von Dieselpartikelfiltern (DPF). Das Unternehmen bietet seitdem maßgeschneiderte Lösungen für Hersteller, Händler, Werkstätten und Anwender. Gereinigt werden alle Dieselpartikelfilter, herstellerübergreifend vom PKW bis zum Schiff – für alles was mit einem Dieselmotor angetrieben wird. Die Säulen der Cleantaxx-Reinigung bilden eine taggleiche Logistik, eine umfangreiche Prüfung und Dokumentation, sowie eine zertifizierte und schonende Reinigung innerhalb 24 Stunden.
            </p>
          </div>
        </div>

Die beschriebene Vorgehensweise haben wir für jedes Ereignis angewendet. Im Body haben wir außerdem die verschiedenen JS-libraries bzw. Plugins angegeben, die wir verwenden wollen. Abschließend haben wir einen beispielhaften Footer eingefügt.

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.7.1/gsap.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.7.1/ScrollTrigger.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ScrollMagic/2.0.7/ScrollMagic.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ScrollMagic/2.0.7/plugins/animation.gsap.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ScrollMagic/2.0.7/plugins/debug.addIndicators.js"></script> <!-- später rausnehmen-->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.3/TweenMax.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.3/TweenLite.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.3/TimelineLite.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.3/plugins/CSSPlugin.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.2/plugins/BezierPlugin.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.7.1/MotionPathPlugin.min.js"></script>

    <script src="app.js"></script>
  </body>
  <!--Beispielhafter Footer-->
  <footer>
    <img src="Medien/footer.png">
  </footer>

CSS

Um das Layout unserer Webseite zu formatieren, haben wir ein externes CSS-Stylesheet verwendet. Mithilfe des CSS-Stylesheet können wir Bilder, Box, Container, Video, Header, Textkörper, Links und Logogröße, Farbe und Stil, Aussehen und Positionierung der Timeline sowie den Abstand zwischen Elementen steuern und angeben, wie Elemente positioniert und angeordnet werden sollen. Box-sizing, html Seite, Body und Footer

* {
  box-sizing: border-box;
}

body, html {
  height: 100%;
}

body {
  background: #f9f9f9;
  background-size: cover;
  margin: 0;
  padding: 0;
  font-family: helvetica, arial, tahoma, verdana;
  line-height: 20px;
  font-size: 14px;
  color: black;
}

footer {
  position:relative;
  width: 100% ;
  height:auto;
  overflow: hidden;
}

Intro Formatierung

/**********Intro*******/
.intro {
  height: 100vh;
}

.intro video {
  height: 100%;
  width: 100%;
  object-fit: cover;
  z-index:1;
}

.intro h1 {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 80px;
  color: #2c5697;
}

Images, Hyperlinks, Container

img {
  max-width: 100%;
}

a {
  text-decoration: none;
}

.container {
  max-width: 1100px;
  margin: 0 auto;
}

/*****Überschriften*****/
h1, h2, h3, h4 {
  font-family: "Dosis", arial, tahoma, verdana;
  font-weight: 500;
}

Infobox

/*****Info Box*****/
#info {
  display: block;
  margin-left: 180px;
  margin-right: 180px;
  margin-bottom: 50px;
}

#info h1 {
  text-align: center;
  padding: 10px 0;
  color: #2c5697;
}

#info h2 {
  font-weight: lighter;
  text-align:center;
}

Die gesamte Timeline-Struktur wurde ebenfalls mit CSS erstellt, indem man die Timeline-Breite, Farbe, Länge, Position, Paddings und Margins definiert und die Boxen an den Seiten der Linie anordnet und zwischen den linken und rechten Containern unterscheidet, indem man sie auf eine andere Klasse definiert.

/*****Timeline*****/
#timeline {
  width: 100%;
  margin: 30px auto;
  position: relative;
  padding: 0 10px;
  content: "";
  clear: both;
  display: table;
}

#timeline:before {
  content: "";
  width: 3px;
  height: 100%;
  background: #2c5697;
  left: 50%;
  top: 0;
  position: absolute;
}

#timeline .timelineItem {
  margin-bottom: 50px;
  position: relative;
  overflow: hidden;
}

/**Punkte auf Timeline**/
#timeline .timelineItem .timelineIcon {
  background: #2c5697;
  width: 50px;
  height: 50px;
  position: absolute;
  top: 0;
  left: 50%;
  overflow: hidden;
  margin-left: -23px;
  border-radius: 50%;
}

#timeline .timelineItem .timelineIcon svg {
  position: relative;
  top: 14px;
  left: 14px;
}

/**Grüner Punkt auf Timeline**/
#timeline .timelineItem .timelineIcon2 {
  background: #3ea838;
  width: 50px;
  height: 50px;
  position: absolute;
  top: 0;
  left: 50%;
  overflow: hidden;
  margin-left: -23px;
  border-radius: 50%;
}

#timeline .timelineItem .timelineIcon2 svg {
  position: relative;
  top: 14px;
  left: 14px;
}

/*******Content auf Timeline*******/
#timeline .timelineItem .timelineContent {
  width: 45%;
  background: #fff;
  padding: 20px;
  box-shadow: 0 3px 0 rgba(0, 0, 0, 0.1);
  border-radius: 5px;
  transform: translateX(50%);
}

#timeline .timelineItem .timelineContent h2 {
  padding: 15px;
  background: #2c5697;
  color: #fff;
  margin: -20px -20px 0 -20px;
  font-weight: 300;
  border-radius: 3px 3px 0 0;
}

#timeline .timelineItem .timelineContent.right {
  float: right;
  margin-left: 55%;
  transform: translateX(-50%);
}

Wir haben auch in den CSS-Dateiklassen definiert, die zu einigen Elementen hinzugefügt werden müssen, um Animationen von JavaScript aus anzuwenden. Zum Beispiel die Klasse “appear”, die hinzugefügt wird, damit die Elemente mit der Klasse eingeblendet werden, indem die Deckkraft des Elements mit Leichtigkeit (Opacity) von 0 auf 1 geändert wird.

Die “appear”-Klasse wird auch Elementen mit der Klasse timelineContent hinzugefügt, um sie mit einem Einblendeffektnach rechts und links und umgekehrt durch Ändern der Deckkraft und der Transformationsachse gleiten zu lassen.

/*****Fade in*****/
#timeline .timelineItem .timelineContent,
#timeline .timelineItem .timelineContent.right {
  transition: opacity 250ms ease-in, -webkit-transform 400ms ease-in;
  transition: opacity 250ms ease-in, transform 400ms ease-in;
  transition: opacity 250ms ease-in, transform 400ms ease-in,
    opacity: 0;
}

#timeline .timelineItem .timelineContent.appear,
#timeline .timelineItem .timelineContent.right.appear {
  transform: translateX(0);
  opacity: 1;
}

.fadeIn {
  opacity: 0;
  transition: opacity 250ms ease-in;
}

.fadeIn.appear {
  opacity: 1;
}

JavaScript

Content einsliden

Die gewünschte Funktion in diesem Code besteht darin, das “Timeline content -div” aus der Richtung des Punktes in der Timeline an seinen Platz gleiten zu lassen und den darin enthaltenen Inhalt (das Bild und den Text) erscheinen zu lassen, sobald der Inhalt nach unten gescrollt wurden.

Um diese Funktion in JavaScript zu erreichen, haben wir das IntersectionObserver() “Interface” (eine Art von Objekt) verwendet. Der IntersectionObserver()-Konstruktor erstellt ein neues IntersectionObserver-Objekt und gibt es zurück. Es benötigt zwei Parameter: callback und options (optional).

Callback-Parameter: Eine Funktion, die aufgerufen wird, wenn der Prozentsatz des Zielelements sichtbar ist, einen Schwellenwert” threshold” überschreitet. Der Callback erhielt als Eingabe zwei Parameter: entries und Observer.

  • entries: Ein Array von IntersectionObserverEntry-Objekten, von denen jedes einen überschrittenen ” threshold” darstellt und entweder sichtbar wird als der durch diesen ” threshold” angegebene Prozentsatz.
  • Observer: Der IntersectionObserver, für den der Rückruf aufgerufen wird.

Options-Parameter: Ein optionales Objekt, das den observer anpasst.

  • rootMargin: Um sicherzustellen, dass es syntaktisch korrekt ist
  • thresholds: Um sicherzustellen, dass sie alle im Bereich von 0,0 bis einschließlich 1,0 liegen.

In der Callback-Funktion haben wir eine foreach-Schleife definiert, die jeden Eintrag “Entry” durchläuft und prüft, ob der IntersectionObserver schneidet oder nicht. Und um Fehler zu vermeiden, geben wir die Funktion das Befehl “return”, falls sie sich nicht überschneidet. Aber wenn es sich überschneidet, soll die Funktion, die classList (appear) hinzuzufügen, die wir in CSS erstellt haben, die die Elemente erscheinen oder von links und rechts gleiten lässt. Danach haben wir die Funktion erstellt haben nachgerufen mit der (unobserved) Methode, damit die Funktion stoppt, sobald sie ihr definiertes Ziel erreicht.

//-----------fadeIn und von Seiten einsliden---------------
const faders = document.querySelectorAll(".fadeIn");
const sliders = document.querySelectorAll(".timelineContent");

const appearOptions = {
  threshold: 1,
  rootMargin: "0px 0px -50px 0px"
};

//-------------------observer für fadeIn--------------------
const appearOnScroll = new IntersectionObserver(function(
    entries,
    appearOnScroll
  ) {
    entries.forEach(entry => {
      if (!entry.isIntersecting) {
        return;
      } else {
        entry.target.classList.add("appear");
        appearOnScroll.unobserve(entry.target);
      }
    });
  },
  appearOptions);

  faders.forEach(fader => {
    appearOnScroll.observe(fader);
  });

Und weil die definierten Optionen zu schnell waren, um bemerkt zu werden, haben wir den Schwellenwert auf eins und den rootMargin auf: 0px 0px -50px 0px festgelegt Damit die Animation funktioniert, sobald wir zum Inhalt herunterscrollen.

Schließlich, um die Aktion zum Leben zu erwecken, wir rufen es mit einer ForEach-Schleife auf, die in die definierten Elemente (sliders und Faders) geht. und wendet die definierte Funktion (appearOnScroll und slideOnScroll) mit der Methode Observe an.

• sliders: wählt alle Elemente aus, die die Klasse “.timelineContent” haben • faders: wählt alle Elemente aus, die die Klasse “.fadeIn” haben

//------------------Einsliden Options----------------------
const sliderOptions= {
  threshold: 0,
  rootMargin: "0px 0px -50px 0px"
};
//------------Observer für von rechts und links------------
const slideOnScroll = new IntersectionObserver(function(
    entries,
    slideOnScroll
  ) {
    entries.forEach(entry => {
      if (!entry.isIntersecting) {
        return;
      } else {
        entry.target.classList.add("appear");
        slideOnScroll.unobserve(entry.target);
      }
    });
  },
  sliderOptions);

sliders.forEach(slider => {
  slideOnScroll.observe(slider);
});

Video-Animation

Um diese Animation umsetzen zu können werden verschiedene Libraries benötigt. Eine davon ist ScrollMagic, welche es ermöglicht auf Scroll zu animieren. Zusätzlich verwenden wir GSAP (genauer gesagt TweenMax), um den Text animieren zu können. Eine sinnvolle Hilfe die wir benutzt haben ist die das debug Plugin von ScrollMagic namens “addIndicators”, welches rechts am Bildschirm Indikatoren für die Trigger einfügt (wie der Name schon sagt).

Um ScrollMagic zu implementieren wird ein Controller im Code benötigt. Anschließend kann man anfangen die verschiedenen Szenen zu erstellen und dem Controller hinzuzufügen, wobei jede Szene ein Teil vom Code ist, den man animieren möchte.

Scrollytelling Bild 3
Indikatoren rechts

Die erste Szene beschäftigt sich mit der Animation des Videos. In der Szene gibt es verschiedene Optionen, wie die Dauer (wie viele Pixel soll gescrollt werden), das Trigger-Element (wann soll die Animation starten, wobei 0=oben, 0.5=Mitte, 1=unten) und die Trigger-Hook (wo auf dem Bildschirm soll das Element getriggert werden. Sobald der “Haken” auf das Element trifft, wird es animiert.

Anschließend muss die Szene durch “.addTo(controller);” dem Controller hinzugefügt werden. Um einen Pin zu setzen, damit das Video für die Dauer der Animation haften bleibt verwenden wir “.setPin(intro);”. Hier kann man ebenfalls die Indikatoren für Hook, Start und Ende mithilfe von “.addIndicators();” anzeigen lassen.

const intro = document.querySelector(".intro");
const video = intro.querySelector("video");
const text = intro.querySelector("h1");

//----------------------ScrollMagic-------------------------
const controller = new ScrollMagic.Controller();

//------------------------Szenen----------------------------
//------------------------Video-----------------------------
let scene = new ScrollMagic.Scene({
    duration: 5000,
    triggerElement: intro,
    triggerHook: 0
  })
  .addIndicators()
  .setPin(intro)
  .addTo(controller);

Nach allen Szenen animieren wir nun das Video, wozu wir einige Variablen benötigen. Da wir nicht wollen, dass das Video beim scrolleln abgehackt aussieht, programmieren wir einen ease-Effekt. Mithilfe von einem Delay, wird das Video nachdem man aufhört zu scrollen noch kurz weiter abgespielt.

  • accelamount = ease-Effekt am Ende (das was von den Frames noch übrig ist)
  • scrollpos = Wo gescrollt wird
  • delay = Soll aufholen wohin wir scrollen

(Um Sekunden zu erhalten, teilen wir die Scroll-Position durch 1000.) Als letztes setzen wir ein Intervall, in dem wir dem delay das hinzufügen was gescrollt wird und es um 0.1 beschleunigen. Um den ease-Effekt nun zu erhalten geben wir an, dass video.currentTime = delay sein soll.

//--------------------Video Animation-----------------------
let accelamount = 0.1;
let scrollpos = 0;
let delay = 0;

scene.on("update", e => {
  scrollpos = e.scrollPos / 1000;
});

setInterval(() => {
  delay += (scrollpos - delay) * accelamount;
  console.log(scrollpos, delay);

  video.currentTime = delay;
}, 43.5);

Um nun den Text zu animieren, sodass dieser nicht die ganze Zeit in der Mitte des Video bleibt, erstellen wir eine neue Szene. In der neuen Szene geben wir erneut an, wie weit gescrollt werden soll, was das Trigger-Element ist und wo die Trigger-Hook sein soll. Um es zu animieren brauchen wir Tweenmax, in dem wir angeben, dass der Text innerhalb von 3 Sekunden von einer opacity (Deckkraft) von 1 zu einer opacity von 0 gehen soll. In der soeben erstellten Szene setzen wir nun noch einen Tween, damit der Text nicht einach nach 3 Sekunden verschwindet, sondern durch scrollen verschwindet.

//---------------------Text Animation-----------------------
const textAnim = TweenMax.fromTo(text, 3, {opacity: 1}, {opacity: 0});

let scene2 = new ScrollMagic.Scene({
    duration: 3000,
    triggerElement: intro,
    triggerHook: 0
  })
  .setTween(textAnim)
  .addTo(controller);

Content anpinnen

Damit wir den Content, also die Boxen mit dem Inhalt, anpinnen können, brauchen wir jeweils eine ScrollMagic-Szene pro Box. Wie auch schon bei der Szene des Videos, geben wir die Dauer, das Trigger-Element und die Position der Trigger-Hook an. In diesem Fall wollen wir, dass jede Box für 300 Pixel angepinnt wird (es also trotz scrollen hängen bleibt). In jeder Szene ordnen wir dem Trigger-Element die entsprechende ID zu, sodass die Boxen nacheinander angepinnt werden.

//---------------------Pin Content--------------------------
var scene4 = new ScrollMagic.Scene({
    duration: 400,
    triggerElement: "#content1",
    triggerHook: 0.1
  })
  .setPin("#content1")
  .addIndicators({name: "1 (duration: 400)"})
  .addTo(controller);

var scene5 = new ScrollMagic.Scene({
    duration: 400,
    triggerElement: "#content2",
    triggerHook: 0.1
  })
  .setPin("#content2")
  .addIndicators({name: "2 (duration: 400)"})
  .addTo(controller);

//-----------------Und so weiter...--------------------

Die gleiche Methode verwenden wir auch, um den grünen Punkt in der Mitte anzupinnen und mitlaufen zu lassen.

//----------------------Pin Punkt---------------------------
var scene3 = new ScrollMagic.Scene({
    duration: 6200,
    triggerElement: ".timelineContent",
    triggerHook: 0.1
  })
  .setPin(".timelineIcon2")
  .addIndicators({name: "Punkt"})
  .addTo(controller);

Anmerkung

Das in unserer Animation verwendete Unternehmen Cleantaxx weiß von unserem Projekt, wir haben es jedoch nicht in Zusammenarbeit mit dem Unternehmen erstellt, sondern eigenständig und rein für unsere MM-SYS Abgabe. Wir sind nicht Urheber des Contents, welches wir in der Animation verwendet haben, haben jedoch die Erlaubnis bekommen den Content ihrer Seite zu verwenden. Nach Absprache mit uns dürfte das Unternehmen Cleantaxx die Animation zu eigenen Zwecken verwenden, falls gewünscht.

Quellen


Dieser Beitrag ist im Studiengang Informationsmanagement an der Hochschule Hannover im Rahmen des Kurses Entwicklung von Multimediasystemen (Sommersemester 2021, Amy Linh Hoang,  Prof. Dr.-Ing. Steinberg) entstanden. Verwendete Techniken sind HTML5, CSS3 und JavaScript. Die besten Tutorials stellen wir Euch hier in den nächsten Wochen nach und nach vor.

Tutorial: Mini Game “Magic Garten” – Blumen pflanzen als interaktive Animation

eitragsbild Tutorial: Mini Game “Magic Garten” - Blumen pflanzen als interaktive Animation

Autorinnen: Maria Olberg  und Elisabeth Rutt 


In diesem Tutorial zeigen wir, wie man eine interaktive Animation entwickeln kann. Wir stellen dazu unsere Animation vor und gehen auf das Konzept, Techniken und Besonderheiten der Animation sowie Ausschnitte des Quellcodes ein.

Link zur Animation

ZIP-Ordner zum Downloaden

Inhaltsverzeichnis

Das Konzept unserer Animation

Bei unserer Animation handelt es sich um das kleine, interaktive Spiel “Magic Garten”, durch das sich der User mit Hilfe von Buttons weiter klicken kann. Das Ziel des Spiels ist es, Blumen in einem Garten zu pflanzen und diese wachsen zu lassen. Dabei kann man zwischen drei verschiedenen Arten von Blumen wählen. Außerdem können die Blumen u.a . auch mit Hilfe einer Gießkanne gegossen werden, damit sie weiter wachsen.

Die Idee zu dieser Animation kam dadurch, dass wir, unabhängig von der Person, die es spielt, für jeden User einen interessanten Zeitvertreib für Zwischendurch entwickeln wollten. Vor allem, soll sie aber Kindern Spaß machen. Die Darstellung der Elemente ist im Comic-Stil gehalten und die Hintergrundmusik verstärkt den Spiele-Charakter.

Das Ziel der Animation ist es, den User mit Hilfe von interaktiven Elementen durch die einzelnen Schritte des Spiels zu führen, damit er im Magic Garten die Blumen pflanzen kann. Dazu gibt es einen helfenden Charakter: eine Biene “Maja”, die den User mit hilfreichen Informationen zu den jeweiligen Schritten des Spiels versorgt. Über die interaktiven Buttons gelangt er entweder zum nächsten Schritt des Spiels oder erhält Anweisungen, was im jeweiligen Schritt zu tun ist. Die Anweisungen erscheinen mit Hilfe von visuellen Effekten in einer Sprechblase.

Insgesamt erfüllt die Animation daher zwei Funktionen: Die erste Funktion bezieht sich auf den Inhalt der Animation und ist das Spiel an sich, bei dem der User die Blumen einpflanzen und wachsen lassen soll sowie das Erzählen der Story, indem sich der User durch die einzelnen Szenen klickt. Die zweite Funktion ist die Optik, die den User zum Spielen animieren soll. Dafür wurden Elemente und visuelle Effekte eingebaut, die das Spiel attraktiver gestalten, wie z. B. die Biene, die den User durch das Spiel begleitet und die lachende Sonne, die über den Garten scheint.

Die Animation besteht aus 4 Szenen, die den User vom Intro bis zu den fertig gewachsenen Blumen leiten. Außerdem sind im Spiel auch Entscheidungen für den User eingebaut, die den weiteren Verlauf der Animation bestimmen.

Aufbau der Animation

Die Animation besteht insgesamt aus 4 aufeinander folgenden Szenen:

Erste Szene: Start und Intro

Der erste Teil der Animation ist der Startbildschirm mit dem Titelbild des Spiels. Hier befindet sich der “Play”-Button, über den der User zur ersten Szene gelangt. Zu Beginn erscheint die Biene “Maja”, die sich vorstellt dem User eine kurze Einführung in das Spiel gibt. Anschließend fordert sie ihn dazu auf, mit dem Spiel zu beginnen und Blumen zu pflanzen. Dazu erscheint der Button “Blumen auswählen”, über den der User in die zweite Szene gelangt, um die Art der Blume, die er pflanzen möchte, auszuwählen.

MagicGarten Bild
Weiterführender Button zum Auswählen der Blumen

Zweite Szene: Auswählen und Pflanzen von Blumen

Sobald der User auf “Blumen auswählen” geklickt hat, erscheinen drei Beutel mit verschiedenen Blumensamen, die Lieblingsblumen von Maja. Jeder Beutel zeigt auch das Bild der jeweiligen Blume. Fährt der Spieler mit der Maus über die Bilder der Blumen, sagt Maja auch etwas zu der jeweiligen Blumenart über ihre Sprechblase. Unter jedem Beutel gibt es den Button “Auswählen” und mit einem weiteren Klick auf den ausgewählten Beutel beginnt das Einpflanzen. Der Beutel beginnt anschließend, die Samen automatisch im Garten zu pflanzen und es sprießen bereits kleine Pflanzen.

MagicGarten2
Der User hat die Wahl zwischen drei Blumensorten

Dritte Szene: Blumen gießen und wachsen lassen

Während des Spiels gibt Maja hilfreiche Tipps zum Spielverlauf. In dieser Szene freut sie sich über die eingepflanzten Blumen und macht auf die Gießkanne aufmerksam, mit der der User die Blumen gießen kann, damit sie wachsen. Der User kann sie Gießkanne anklicken, um sie hoch zu heben und mit der Maus anschließend darüber fahren, um mit dem Gießen zu beginnen. Als nächstes gibt Maja den Tipp, die Blumen nun in der Sonne weiter wachsen zu lassen. Dazu kann der User nun auf die Sonne klicken, die daraufhin beginnt, stärker zu scheinen und über das Blumenfeld zu wandern. Anschließend bietet Maja einen Magic Booster an, um die Blumen doppelt so stark wachsen zu lassen. Der User kann hier zwischen “Ja” und “Nein” auswählen. Je nach dem, welche Antwort der User wählt, ändern sich die Animation und die Reaktion von Maja.

MagicGarten3
Der User wählt beispielhaft Sonnenblumen aus
MagicGarten4
Der User kann die Blumen gießen und durch die Sonne wachsen lassen

Vierte Szene: Outro

In der vierten Szene spielt der User nicht mehr aktiv mit. Maja beginnt mit Hilfe einer Animation über die Blumenwiese zu fliegen. Zum Schluss bedankt sie sich für die Hilfe des Users und schenkt ihm als Dank ein Glas mit Honig. An dieser Stelle ist die Animation beendet und es erscheint der Button “Restart”, wodurch die Seite neu geladen wird und der User wieder zum Intro gelangt.

MagicGarten5
Schlussszene: Maja bedankt sich für die Hilfe und der User kann das Spiel neu starten

Der Quellcode

Verwendete Programmiersprachen für die Animation sind HTML5, CSS3, JavaScript und jQuery. HTML wird für die Gestaltung sowie den Grundaufbau der Seite und des Spiels genutzt. CSS sorgt für die richtige Positionierung, Style-Anpassungen und mit Hilfe von keyframes auch für die kleine Animationen und visuelle Effekte.

Mit Hilfe von JavaScript werden die Objekte je nach Auswahl der Story-Optionen angezeigt bzw. versteckt, der Text bei den Hover-Effekten geändert, die Auswahl der Blumensorte für den weiteren Verlauf des Spiels gespeichert und die Index-Datei so umgeschrieben, dass sie sich automatisch angepasst hat. Außerdem erscheinen die Objekte durch JavaScript nach bestimmten Zeitangaben für ein optimales Spielerlebnis, wie z. B. die automatische Änderungen des Textes und die Bewegung der Gießkanne nach einigen Sekunden. Die Seite besteht aus drei DIV-Containern: Der erste zeigt die Überschrift “Magic Garten”, der zweite das Spielfeld und der dritte zeigt einen Untertitel.

<body>
    <script src="animation.js"></script> <!--Einbindung der JavaScript Datei--> </div>
    <div class="header"> <!--Div-Container wird angezeigt nach dem Spielstart--></div>
    <div id="animation_feld" class="animation_null"> <!--Hauptbereich: interaktives Spiel--></div>
    <div class="biene"> <!--Intro des Spieles und Platziereung der Beiene während des ganzen Spiel--></div>
<div class="footer"> <!--Unterbereich mit Autorennamen und versteckte Audio--> </div> </body>

Gestaltung von besonderen Elementen

Innerhalb der Animation gibt es verschiedene interaktive Elemente, die sich mit Hilfe von Animationen durch die Szenen bewegen und gewisse Aktionen auslösen.

Die verwendeten Bilder sind im PNG-Format und stammen von Pixabay. Die Überschrift und der Untertitel sowie die fertig gewachsenen Blumen wurden selbst in Photoshop erstellt. Damit die Bilder optimal angezeigt werden, wurde der z-Index verwendet. Dieser unterscheidet sich bei den Bildern der jeweiligen Szenen und man sollte ihn so anpassen, dass Bilder möglichst gut dargestellt werden.

Bewegende Elemente und visuelle Effekte

Maja

Damit Maja dem User durch das Spiel helfen kann, wird sie durch von Keyframes zum Leben erweckt. Mit Hilfe des JavaScript Befehls .show() und .hide() wird sie passend zur jeweiligen Szene eingesetzt bzw. versteckt. Hierbei gibt es drei verschiedene Keyframe Animationen: Zuerst bewegt sich Maja vom Startbildschirm nach unten auf das folgende Bild, sobald der User das Spiel startet. Damit sie ihn mit nützlichen Informationen durch das Spiel führen kann, erhält sie Sprechblasen, die mit Hilfe der Anweisung

setTimeout(function() {$("#rede_1").css({"visibility": "visible"});}, 2000); 

angezeigt und mit bspw.

setTimeout(function() {$("#text_1").hide();$("#text_2").css({"visibility": "visible"});}, 500); 

nach 5 Sekunden automatisch geändert werden. Die Zeit, nach der sich die Animationen ändern, variiert hier während des Spiels und kann nach Belieben angepasst werden, damit man die Texte gut lesen kann.

    /*Biene startet das Gespräch mit dem User*/
    setTimeout(function() {
        $("#rede_1").css({"visibility": "visible"});
    }, 2000);
    setTimeout(function() {
        $("#text_1").css({"visibility": "visible"});
    }, 2500);
});	
$("#link_1").on("click", function () {
    setTimeout(function() {
        $("#text_1").hide();
        $("#text_2").css({"visibility": "visible"});
    }, 500);
    setTimeout(function() {
        $("#button_choose").css({"visibility": "visible"});
    }, 1500);
});

Die zweite Keyframe Animation lässt Maja auf dem nächsten Bild nach oben wirbeln. Diese Animation wird mit Hilfe des Befehls

$("#honey_biene_2").css({"animation-play-state": "running"}); 

ausgelöst, sobald der User den Button “Blumen auswählen” anklickt. Hier werden die Texte in der Sprechblase wieder mit dem oben stehenden Befehl angezeigt und geändert (die konkreten Befehle ändern sich etwas je nach Text).

/*Übergang zur Szene #1.2
    Start des aktiven Spiels mit Anweisungen an den Spieler
    Elemente werden durch Änderungen des Styles angezeigt oder durch 
    jQuery Funktionen*/
$("#button_choose").on("click", function() {
    $("#button_choose").css({"visibility": "hidden"});
    $("#choose").hide();
    $("#rede_1").css({"visibility": "hidden"});
    $("#text_2").hide();
    $("#honey_biene").css({"visibility": "hidden"});
    $("#honey_biene_2").show();
    $("#honey_biene_2").css({"animation-play-state": "running"});
    setTimeout(function() {
        $("#rede_2").css({"visibility": "visible"});
    }, 1700);
    setTimeout(function() {
        $("#text_3").css({"visibility": "visible"});
    }, 2200);
    $(".sack").fadeIn(3000);
    setTimeout(function() {
        $(".blumen_choose").show();
    }, 3000);

Die dritte Keyframe Animation lässt Maja zum Ende des Spiels über die Blumenwiese fliegen. Mit Hilfe von translateX() bewegt sie sich hin und her und mit scaleX() dreht sie sich während der Animation um, um in die jeweils andere Richtung zu fliegen. Dabei wird das Bild gespiegelt. Ist der Magic Booster nicht ausgewählt, fliegt Maja standardmäßig durch die Eigenschaft animation-iteration-count: 1; einmal Hin und Her. Wenn der Magic Booster ausgewählt ist, dann fliegt sie zweimal Hin und Her. Dies wird mit Hilfe einer if-Anweisung angepasst.

/*Style Eigenschaften für die Biene im Outro.
Die keyframe-Animation für die fliegende Biene. In JS ist die Anpassung je nach
Antwort des Users*/

#biene_fly {
    position: absolute;
    z-index: 15;
    height: 15%;
    bottom: 60%;
    left: 22%;
    display: none;
    
    animation-name: bee_fly; 
    animation-duration: 5s;
    animation-timing-function: ease;
    animation-delay: 0s;
    animation-iteration-count: 1;
    animation-fill-mode:forwards;
    animation-play-state: running;
}

@keyframes bee_fly {
    0%  { transform: translateX(0px) scaleX(-1)}
    25%	{ transform: translateX(380%) scaleX(-1)}
    50%	{ transform: translateX(380%) scaleX(1)}
    100%  { transform: translateX(-10%) scaleX(-1)}
    
}

Säcke mit Blumensamen

Der Hover-Effekt bei den Blumensäcken wird mit der Anweisung $(“#rose_sack”).mousemove() erzeugt. So ändert sich der Text in der Sprechblase, wenn man mit der Maus über ein bestimmtes Element (in dem Fall ist es die Rose) fährt. Verlässt man mit der Maus diese Stelle wieder, wird der Text mit dem Befehl $(“#rose_sack”).mouseout() wieder automatisch geändert.

/*Information über Blumensorten erscheint, wenn die Maus auf dem Bild ist,
    und verschwindet, wenn die Maus weg ist*/
$("#rose_sack").mousemove(function(evt) {
    $("#text_3").css({"display":"none"});
    $("#text_4").css({"visibility": "visible","display":"block"});	
});
$("#rose_sack").mouseout(function(evt) {
    $("#text_4").css({"display":"none"});
    $("#text_3").css({"display":"block"});
});
$("#sun_sack").mousemove(function(evt) {
    $("#text_3").css({"display":"none"});
    $("#text_5").css({"visibility": "visible","display":"block"});	
});
$("#sun_sack").mouseout(function(evt) {
    $("#text_5").css({"display":"none"});
    $("#text_3").css({"display":"block"});
});
$("#mohn_sack").mousemove(function(evt) {
    $("#text_3").css({"display":"none"});
    $("#text_6").css({"visibility": "visible","display":"block"});	
});
$("#mohn_sack").mouseout(function(evt) {
    $("#text_6").css({"display":"none"});
    $("#text_3").css({"display":"block"});
});

Nach der Auswahl der Blumesorte verschwinden durch die Anweisungen $(“.sack”).fadeOut(1200); und $(“.blumen_choose”).hide();  die Bilder der Säcke, die nicht ausgewählt wurden und der ausgewählte Sack erscheint in der Mitte.

$(".sack").fadeOut(1200);
$(".blumen_choose").hide();

/*Je nach Auswahl wird der Sack mit der richtigen Blume angezeigt. Animation an sich
    ist gleich und startet beim Klick auf jeweiliges Bild*/		
switch (blume) {
    case "Rose":
    setTimeout(function () {
        $(".rose").show();
        $("#sack_blume_rose").on("click", function () {
            $(".sack_blume").css({"animation-play-state": "running"});
            $(".oat_raw").css({"animation-play-state": "running"});
            setTimeout(function () {
                $(".rose").hide();
                $("#text_7").css({"display":"none"});
                $("#text_8").css({"visibility": "visible","display":"block"});
            }, 7200);
        })
    }, 1500);
    break;

Beim Klick auf den Sack wird die Keyframe Animation aktiviert, wodurch er sich bewegt, um die Blumensamen auf der Wiese zu verteilen. Die Anweisung besteht aus 7 Schritten, in denen translateX(), translateY() und rotate() verwendet werden,sodass er sich zum Verteilen über die Wiese bewegt und dabei auch die Ausrichtung nach links und rechts ändert. Bei der Anweisung für die Blumensamen, die aus dem Sack in die Erde fallen, benutzt man zusätzlich scale(). Nachdem die Animation fertig abgespielt ist und damit die Samen verteilt sind, verschwindet der Sack automatisch.

/*Positionierung und Animation für alle Säcke in der Szene sind gleich*/

.sack_blume {
    position:absolute;
    top: 50%;
    left: 40%;
    height: 30%;
    z-index: 11;
    cursor:pointer;
    
    animation-name: sack_hoch; 
    animation-duration: 7s;
    animation-timing-function: ease;
    animation-delay: 0s;
    animation-iteration-count: 1;
    animation-fill-mode:forwards;
    animation-play-state: paused;
}

/*Animation mit mehreren Bewegungen*/
@keyframes sack_hoch {
    from  { transform: translateX(0px) translateY(0px)}
    18%	{ transform: translateX(-100px) translateY(-250px) rotate(-90deg)}
    36%	{ transform: translateX(-325px) translateY(-130px) rotate(-120deg)}
    52%	{ transform: translateX(100px) translateY(-250px) rotate(-120deg)}
    70%	{ transform: translateX(100px) translateY(-130px) rotate(-120deg)}
    88% { transform: translateX(350px) translateY(-250px) rotate(120deg)}
    to  { transform: translateX(400px) translateY(-130px) rotate(120deg)}
}
/*Positionierung und Animation für Blumensamen in der Szene sind gleich
Die Animation wiederholt die Bewegung des Sackes, mit der Ausnahme beim vorletzten
Schritt:
    Blumensamen verschwinden und erscheinen am Ende, wänhrend Sack sich umdreht*/
.oat_raw {
    position:absolute;
    top: 51%;
    left: 46%;
    height: 10%;
    z-index: 12;
    
    animation-name: oat_hoch; 
    animation-duration: 7s;
    animation-timing-function: ease;
    animation-delay: 0s;
    animation-iteration-count: 1;
    animation-fill-mode:forwards;
    animation-play-state: paused;
}

/*Animation mit mehreren Bewegungen*/

@keyframes oat_hoch {
    from  { transform: translateX(0px) translateY(0px)}
    18%	{ transform: translateX(-150px) translateY(-180px) rotate(-90deg)}
    36%	{ transform: translateX(-375px) translateY(-40px) rotate(-120deg) scale(0.85)}
    52%	{ transform: translateX(50px) translateY(-140px) rotate(-120deg) scale(0.75)}
    70%	{ transform: translateX(50px) translateY(-50px) rotate(-120deg) scale(0.7)}
    88% { transform: translateX(390px) translateY(-180px) rotate(120deg)scale(0.0)}
    to  { transform: translateX(465px) translateY(-40px) rotate(120deg) scale(0.65)}
}

Positionierung und Animation der Blumensäcke

Wachsende Blumen

Die Blumen wachsen in dem Spiel mit Hilfe einer Animation mittels JavaScript. Dazu gibt es für die verschiedenen Stadien des Wachstums verschiedene Bilder, die automatisch mit Hilfe von Anweisungen, wie bspw. $(“.plant_small”).attr(“src”, “media/grass.png”); ausgetauscht werden und anschließend durch die Anweisung $(“.plant_small”).css({“animation-play-state”:”running”}); animiert werden. Dadurch verändert sich das Wachstum der Blumen durch Animationen im Laufe des Spiels automatisch. Die Auswahl der Blumensorte zu Beginn des Spiels wird mit Hilfe einer Switch-Anweisung für den restlichen Spielverlauf übernommen

        /*Funktion für Blumen. Bilder sind von der Auswähl abhängig*/
        switch (blume) {
            case "Rose":
                $(".plant_small").attr("src", "media/Rose_Bush.png");
                break;
            case "Sonnenblume":
                $(".plant_small").attr("src", "media/Sunflower_Bush.png");
                break;
            case "Mohnblume":
                $(".plant_small").attr("src", "media/Poppy_Bush.png");
                break;
        }
        $("#sonne").css({"animation-play-state": "running"});
        $("#text_12").css({"display":"none"});
        $("#text_13").css({"visibility": "visible","display":"block"});
}, 2500);
});

Gießkanne

Für die Animation der Gießkanne werden sowohl Keyframe als auch jQuery Animationen verwendet. Bei den keyframes handelt es sich um rein visuelle Effekte, bei der sich die Gestaltung der Gießkanne ändert. Mit Hilfe von bspw. transform:scale() ändert sich ihre Größe, durch opacity ändert sich die Transparenz und durch animation-iteration-count:2 wiederholt sich diese Animation zweimal. Die Bewegung wird anhand von .animate() realisiert, sodass sich die Kanne über die Pflanzen bewegt. Dabei ändert sich mit Hilfe der Anweisung .mousemove ihre Ausrichtung, sobald der User mit der Maus über sie fährt und mit .mouseleave verschwindet die Gießkanne. Gleichzeitig wird auf diese Weise auch die Animation für die Wassertropfen ausgelöst.

/*jQuery-Animation für die Gießkanne und Style Änderungen für Interaktivität*/
$("#giesskanne").click(function() {
    $("#giesskanne").animate({
        bottom: "50%",
        left: "50%",
        }, 4000, "linear", function() {
            /*Animation wird aktiviert nur beim Bewegen auf dem Bild*/
            $("#giesskanne").mousemove(function(evt) {
            $(".wasser_tropfen").css({"display":"block"});
            $("#giesskanne").css({"transform":"rotate(-30deg)"});
            $(".wasser_tropfen").animate({
                bottom: "-55%"
                }, 2500 )
            });
            /*Ausschaltung der Animation*/
            $("#giesskanne").mouseleave(function(evt) {
                $(".wasser_tropfen").css({"display":"none"});
                setTimeout(function () {
                    $("#giesskanne").hide('slow');
                }, 3000);
            });
        });

Sonne

Die Sonne bewegt sich im Spiel mit Hilfe von keyframes automatisch über die Blumenwiese bewegt und scheint stärker, sobald der User sie anklickt.

/*Durchgehende Animation für besseres Spiel. Einige Style Egenschaften werden später 
via JS ergänzt*/ 
#sonne {
    position: absolute;
    z-index: 2;
    bottom: 60%;
    left: 70%;
    height: 25%;
    cursor:pointer;
    
    animation-name: sonne_bewegung; 
    animation-duration: 45s;
    animation-timing-function: ease;
    animation-delay: 0s;
    animation-iteration-count: 1;
    animation-fill-mode:forwards;
    animation-play-state: running;
}

@keyframes sonne_bewegung {
    from  { transform: translateX(0px) translateY(0px)}
    to  { transform: translateX(-500px) translateY(-20px)}
}

Nachdem die Gießkanne eingesetzt wird, stoppt die Bewegungs-Animation der Sonne, damit der User sie leichter anklicken kann. Bei dem Klick auf die Sonne wird sie größer und scheint stärker, um sie hervorzuheben. Das geschieht mit Hilfe der Anweisung 

$("#sonne").click(function() {$("#sonne").css({"height":"30%", "filter":"drop-shadow(0px 5px 25px #FFD700)"});
/*Animation für die Sonne wird aktiviert beim Klick. Für den richtigen
    Spielablauf ist das Anklicken der Sonne nur nach der Animation mit der 
    Gießkanne möglich*/
$("#sonne").click(function() {
    $("#sonne").css({"height":"30%", "filter":"drop-shadow(0px 5px 25px #FFD700)"});
    $(".plant_small").css({"animation-play-state":"running"});

Magic Booster

Der Magic Booster erscheint mit Hilfe der jQuery Anweisung $(“#booster”).show();. Die Animation wird automatisch ausgelöst, sobald er vom User ausgewählt wurde und läuft mit Hilfe der Anweisung animation-iteration-count: infinite; unendlich in einer Schleife. Durch die Anweisung $(“#booster”).fadeOut(4000); verschwindet der Booster automatisch innerhalb von 4 Sekunden. Auch hier kann man die Zeiteinstellungen nach Belieben und Funktionalität anpassen.

/*Magic Bosster wird angezeigt nur bei "Ja"-Antwort
Animation ist unendlich und startet mit der Erscheinung des Bildes*/

#booster {
    position: absolute;
    z-index: 15;
    height: 150;
    bottom: 70%;
    left: 40%;
    
    animation-name: magic_booster; 
    animation-duration: 3s;
    animation-timing-function: ease;
    animation-delay: 0s;
    animation-iteration-count: infinite;
    animation-fill-mode:backwards;
    animation-play-state: running;
}

/*Animation zeigt den Effekt indem der Booster über die Blumenwiese gegossen wird.*/

@keyframes magic_booster {
    0%  { transform: scale(0.5) rotate(0deg)}
    50%	{ transform: scale(1) rotate(180deg)}
    100%  { transform: scale(0.5) rotate(-180deg)}
    
}

Interaktive Buttons

Sobald die Maus über die Buttons fährt, erscheint ein Hover-Effekt, in dem sich die Farben ändern. Der Hover-Effekt wird durch diese Eigenschaft umgesetzt:

button:hover {

transition: 3s in-back;

background-image: radial-gradient(#ffd700, #ff5433);

}

Außerdem wird der Cursor durch die Eigenschaft cursor:pointer; in Form einer Hand angezeigt, sobald der User mit der Maus über interaktive Elemente fährt. Dadurch weiß er, welche Elemente anklickbar und für den Spielverlauf wichtig sind.

Besonderheiten

Eine Besonderheit der Animation ist die Hintergrundmusik, die während des Spiels läuft. Sie startet automatisch, wenn der User das Spiel beginnt. Beginnt der User ein neues Spiel, indem er auf “Restart” klickt, startet auch die Musik wieder neu. Dies wird mit Hilfe des jQuery-Befehls $(“#background_musik”)[0].play(); umgesetzt. Bei der Hintergrundmusik haben wir darauf geachtet, eine lizenzfreie Musik zu nehmen, die zum Thema die Spiels passt. Dafür haben wir den Titel “The Small Farm” gewählt. Mit dem Befehl $(“#background_musik”)[0].pause(); endet die Musikwiedergabe zum Ende des Spiels automatisch.

Eine weitere Besonderheit ist das Responsive Design. Dieses wird eingesetzt, damit die Animation auf allen Desktopgrößen richtig angezeigt wird und gespielt werden kann. Das Responsive Design wurde mit Hilfe von CSS angepasst und bezieht sich auf alle Elemente im Quellcode. Dies wurde mit der Eigenschaft position: absolute; sowie der Anpassung der Abstände durch bottom: left: right: top: height: in Prozent-Angaben umgesetzt. Die Eigenschaft font-size: 1.05vw; führt dazu, dass auch die Texte optimiert dargestellt werden.


Dieser Beitrag ist im Studiengang Informationsmanagement an der Hochschule Hannover im Rahmen des Kurses Entwicklung von Multimediasystemen (Sommersemester 2021, Amy Linh Hoang,  Prof. Dr.-Ing. Steinberg) entstanden. Verwendete Techniken sind HTML5, CSS3 und JavaScript. Die besten Tutorials stellen wir Euch hier in den nächsten Wochen nach und nach vor.

Forty in 49: Ein Spiel für Gedächtnis und räumliches Vorstellungsvermögen

Beitragsbild forty in 49 - ein Spiel für das Gedächtnis und das räumliche Vorstellungsvermögen

Autor: Jannis Fortmann

Link zum Spiel
Zip Datei zum Download

Inhalt

Von Anfang an war es mein Plan, ein simples und optisch ansprechendes kleines Spiel zu designen, bei dem Animationen sinnvoll zum Einsatz kommen.

Nach ein paar verworfenen Ideen entschied ich mich für diese Variante, bei der die räumliche Wahrnehmung und das Gedächtnis des Spielers auf die Probe gestellt wird. Das Konzept des Spiels ist angelehnt an einen Intelligenztest für Schimpansen, bei dem deren Arbeitsgedächtnis getestet wird. Bei diesem werden aufeinanderfolgende Zahlen auf einem Bildschirm abgebildet, diese zahlen müssen dann in der richtigen Reihenfolge gedrückt werden. Nachdem die erste Zahl gedrückt wurde, werden alle anderen hinter identisch aussehenden Feldern versteckt.

Mein Spiel „forty in 49“ funktioniert nach einem ähnlichen Prinzip. Anstatt einer bestimmten Reihenfolge geht es hierbei aber um die Position der Felder. In einem Raster sind 7×7 Felder angeordnet,. Zu Beginn des Spiels verschwinden 40 der 49 Felder für wenige Sekunden. Der Spieler muss sich die Position der übrigen 9 Felder (Bomben) merken und diese vermeiden. Das Ziel ist es, möglichst viele der 40 Felder anzuklicken. Klickt man auf eine Bombe, ist das Spiel vorbei.

Animationen

Die Animationen erfüllen sowohl funktionale als visuelle Zwecke.

Um die Orientierung zu vereinfachen, werden Felder größer und ändern ihre Farbe, wenn der Mauszeiger darüber hovert. Wird ein Feld angeklickt, wird es entweder grün und verschwindet in einer rotierenden Animation, oder es wird rot und dreht sich langsam um 180° um die y-Achse und um 45° um die z-Achse. Dadurch wird dem Spieler visuell verdeutlicht, ob er richtig lag. Um das Ganze auditiv zu unterstützen, gibt es für den Klick auf die zwei Arten von Feldern jeweils einen Soundeffekt.

Ist das Spiel vorbei (alle richtigen Felder oder eine Bombe wurden geklickt), erscheint eine Karte mit der Punktzahl. Sie bewegt sich von unten nach oben und landet mittig über dem Spielfeld. Außerdem gibt es natürlich die fade-out/fade-in Animation am Anfang, ohne die das Spiel überhaupt nicht möglich wäre.


Dieser Beitrag ist im Studiengang Informationsmanagement an der Hochschule Hannover im Rahmen des Kurses Entwicklung von Multimediasystemen (Sommersemester 2021, Amy Linh Hoang,  Prof. Dr.-Ing. Steinberg) entstanden. Verwendete Techniken sind HTML5, CSS3 und JavaScript. Die besten Tutorials stellen wir Euch hier in den nächsten Wochen nach und nach vor.

Tutorial: Ein Memory-Spiel

Beitragsbild Tutorial: Memory-Spiel

Autorin: Linda Görzen 


Memory-Spiel

Konzept

Ziel dieses Projektes war die Erstellung eines Spiels , das durch sämtliche Animationen bzw. Animationseffekte modern und dynamisch wirken sollte. Memory ist ein Spiel, das weltbekannt und nicht zu kompliziert zu programmieren ist. Im Internet gibt es sämtliche frei verfügbare Beispielcodes. Einer davon wurde in diesem Projekt verwendet.

Der Spielvorgang läuft folgendermaßen ab: Der Start des Spieles muss durch das Klicken eines Buttons ausgelöst werden. Danach hat man 30 Sekunden Zeit, um das Spiel zu gewinnen. Wenn man es nicht schafft, bricht das Spiel nach 30 Sekunden ab und man hat somit verloren.

Inhaltsverzeichnis

Aufbau

Das Spiel besteht aus drei Ebenen: 1. Startbildschirm 2. Spiel 3. Endbildschirm: gewonnen oder verloren

Für die Einfachheit und Übersichtlichkeit besteht das Spiel aus zwei HTML-Dateien. Die Datei game_start.html beinhaltet die erste Ebene. Die Datei game.html die Ebenen zwei und drei.

Startbildschirm

Hier erscheinen nacheinander ein Button, der zum Spiel weiterführen soll, ein Austronaut und ein Raumschiff. Im Hintergrund bewegen sich von oben nach unten und von unten nach oben kleine Punkte, die Sterne repräsentieren sollen. Der Button hat einen Hover-Effekt: Wenn man über ihn mit der Maus fährt, verändert sich der Text und die Farbe .

Memory-Spiel Bild 1
Abbildung 1: Startansicht

Memory-Spiel

Das eigentliche Spiel befindet sich im rechten Drittel des Bildschirms und besteht aus zwölf Karten, welche in drei Reihen gestapelt sind. Links vom Spiel sieht man den Astronauten und eine Sprechblase. Über den Astronaut schwebt ein Button, mit dem das Spiel neu gestartet werden kann. Beim Klicken auf die Karten, erscheint zusätzlicher Text in der Sprechblase. Nach ein paar Sekunden verschwindet dieser.

Memory-Spiel Bild 2
Abbildung 2: Spiel

Gewonnen – Endbildschirm

Wenn man innerhalb der 30 Sekunden alle Paare gefunden hat, verschwinden die Sprechblase und die Karten. Stattdessen blendet das Programm Konfetti und Überschrift “6/6! Gut gemacht gemacht!” ein. Im Hintergrund wird einmalig Triumpfsound “Ta-Da” abgespielt.

Memory-Spiel Bild 3
Abbildung 3: Spiel gewonnen

Verloren – Endbildschirm

Wenn 30 Sekunden verstrichen sind und nicht alle Paare gefunden wurden, dann bricht das Spiel ab und statt der Sprechblase und der Karten erscheint die Überschrift “Verloren” und in der Sprechblase steht jetzt “Schande!”. Der Astronaut fängt zu weinen an.

Memory-Spiel Bild 4
Abbildung 4: Spiel verloren

Erläuterung des Codes

Erste Ebene

HTML und javascript

Für die Erzeugung bestimmter Animationen lohnt es sich auf JS-Bibliotheken zurückzugreifen. In diesem Projekt wurde GSAP eingesetzt. Bei GSAP handelt es sich um eine JS-Bibliothek für zeitleistenbasierte Animationen . Die GSAP Funktionen lassen sich über CDN laden, indem man ihn im Head-Bereich einbindet.

Der CDN für allgemeine Funktionen reicht aber nicht aus, um Bewegung entlang eines vorgeschriebenen Pfades zu erzeugen. Der Code muss zusätzlich mit dem Plugin für GSAP-MotionPath ergänzt werden.

  <head>
    <meta charset="utf-8" />
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<meta name="description" content="Memory game done with javascript and css animation">
    <meta name="keywords" content="game, interactive, avascript, css, html">
    <title>Memory Game</title>
	<link rel="stylesheet" href="styles_start.css"> <!-- Nur für game_start -->
	<script src="https://cdn.jsdelivr.net/web-animations/latest/web-animations.min.js"></script> <!-- Polyfill -->
	<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.7.0/gsap.min.js"></script> <!-- Core Green Sock-->
	<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.7.0/MotionPathPlugin.min.js"></script> <!--Plugin für MotionPath von Green Sock-->
  </head>

Der HTML-Grundgerüst der ersten Ebene ist simpel aufgebaut. Es wurden nacheinander Elemente eingefügt, die in bestimmten Reihenfolge sichtbar werden sollten.

	<div class="sternenhimmel-bild">	
		<!-- Sterne, die sich von unten nach oben und umgekehrt bewegen, insperiert von https://www.youtube.com/watch?v=aywzn9cf-_U  -->
				<div id="stars"></div>
				<div id="stars2"></div>
				
				<div id="stars_2"></div>
				<div id="stars2_2"></div>	
			<img id="astro" src="Bilder/astro.png">
		
			<div>
				<a href="game.html"><button id="button1"></button></a>
			</div>
			
			<img id="spaceship" src="Bilder/futurama.png">
	</div>

Auf der ersten Ebene befindet sich der Javascript-Code in der HTML-Datei . Dank GSAP kann man den Code für die schwebende Bewegung des Astronauten kurz halten. Mit GSAP wird auch das Erscheinen des Buttons animiert. Man darf nicht vergessen den MotionPathPlugin zu registrieren, sonst wird motionPath nicht ausgeführt. Mit gsap.timeline() geben wir die Reihenfolge der Animationen vor. Zuerst soll der Button und dann der Astronaut erscheinen. Der Astronaut soll daraufhin im Kreis um den Button schweben und dann stehen bleiben.

		<script>
				//Plugin registrieren
				gsap.registerPlugin(MotionPathPlugin);
				
				//#astro 
				gsap.timeline().from('#button1', {opacy: 0.05, scale: 0, rotation: 10, ease:'back', duration: 1.5, })
							   .from("#astro", {duration: 2, x: 200, opacity: 0, scale: 0.5, rotation: 10})
							   .to("#astro", {
							  duration: 13, 
							  motionPath:{
							  path: [{x:220, y:440, scale:0.5, rotation:10}, {x:780, y:240,scale: 0.7, rotation:-30}, {x:470, y:10, scale:1, rotation:5}]
							  }
							});

				MotionPathHelper.create("#astro")
		</script>
CSS

Der Inhalt des Buttons wurde mit dem Pseudoelement ::after eingefügt. Das Ziel war es, den Text beim Hovern über den Button zu ändern. Beim Hovern sollen auch die Hindergrundfarbe und der Schatten sich verändern. Da :hover::after nur den Inhalt (also Text) ansteuert, muss man noch einmal :hover benutzen.

#button1 {
	display:inline-block;
	box-sizing: border-box;
	min-width: 11em;
	border-width: 4px;
	border-radius: 20px;
	background-color: rgba(204, 102, 255, 0.55);
	border-color: rgba(239, 204, 255);
	text-align: center;
	font-size: 31px;
	padding: 25px;
	position: absolute;
	top: 53%;
	left: 50%;
	transform: translate(-50%, -50%);
	box-shadow: 0 8px 16px 0 rgba(0,0,0,0.3), 0 6px 20px 0 rgba(0,0,0,0.29);
}

#button1::after{
    content:'Eine Runde spielen?';
}
#button1:hover::after{
    content:"Los geht's!";
}

#button1:hover {
    background-color: rgba(251, 41, 253, 0.9);
    cursor: pointer;
      box-shadow: 0 8px 70px 0 rgba(255,250,250,0.3), 0 8px 70px 0 rgba(255,250,250,0.3),  0 8px 70px 0 rgba(255,250,250,0.3), 0 8px 70px 0 rgba(255,250,250,0.3);
    transition: background-color 2s ease-out;
}

Um den Hintergrund dynamischer wirken zu lassen, werden zwei Sterne-Animationen hinzugefügt. Die Idee der Erstellung der Sterne mit box-shadow und sie dann in Bewegung zu setzten stammt von diesem Youtube-Video . Im Spiel bewegen sich die Sterne sowohl von oben nach unten als auch von untern nach oben. Ermöglicht wird es durch zwei Keyframes.

/* von unten nach oben */
#stars {
	width: 3px;
	height: 3px;
	border-radius: 50%;
	background:transparent;
	animation: animStar 50s linear infinite;
	box-shadow: 789px 1341px #fff, 
				364px 52px #fff, 
				353px 596px #fff,
				1412px 376px #fff, 
				451px 625px #fff, 
				521px 1931px #fff, 
				1087px 1871px #fff,
				36px 1546px #fff, 
				132px 934px #fff, 
				1698px 901px #fff, 
				1418px 664px #fff,
				1448px 1157px #fff,
				1084px 232px #fff, 
				347px 1776px #fff, 
				1222px 343px #fff; /* 15*/
}

#stars2 {
	width: 6px;
	height: 6px;
	border-radius: 50%;
	background:#bb33ff;
	animation: animStar 100s linear infinite;	 
	box-shadow: 1448px 320px #00ffff, 
				1775px 1663px #00ffff, 
				332px 1364px #00ffff,
				878px 340px #00ffff, 
				569px 1832px #00ffff,
				1422px 1684px #00ffff, 
				1946px 1907px #00ffff,
				121px 979px #00ffff, 
				1044px 1069px #00ffff, 
				463px 381px #00ffff, 
				
				423px 112px #ffffb3,
				523px 1179px #ffffb3, 
				779px 654px #ffffb3,
				1398px 694px #ffffb3, 
				1085px 1464px #ffffb3;
}

@keyframes animStar{
	from {
		transform: translateY(0px);
	}
	to {
		transform: translateY(-1000px);
	}
}


/* von oben nach unten*/

#stars_2 {
	width: 3px;
	height: 3px;
	border-radius: 50%;
	background:transparent;
	animation: animStar_2 50s linear infinite;
	box-shadow: 779px 1331px #fff, 
				324px 42px #fff, 
				303px 586px #fff,
				1312px 276px #fff, 
				451px 625px #fff, 
				521px 1931px #fff, 
				1087px 1871px #fff,
				36px 1546px #fff, 
				132px 934px #fff, 
				1698px 901px #fff, 
				1418px 664px #fff,
				1448px 1157px #fff,
				1084px 232px #fff, 
				347px 1776px #fff, 
				1722px 243px #fff; /* 15*/
}

#stars2_2 {
	width: 6px;
	height: 6px;
	border-radius: 50%;
	background:#bb33ff;
	animation: animStar_2 100s linear infinite; 
	box-shadow: 1448px 320px #9acd32, 
				1775px 1663px #9acd32, 
				332px 1364px #9acd32,
				878px 340px #9acd32, 
				569px 1832px #9acd32,
				1422px 1684px #9acd32, 
				1946px 1907px #9acd32,
				121px 979px #9acd32, 
				1044px 1069px #9acd32, 
				463px 381px #9acd32, 
				
				423px 112px #ffffb3,
				523px 1179px #ffffb3, 
				779px 654px #ffffb3,
				1398px 694px #ffffb3, 
				1085px 1464px #ffffb3;
}

@keyframes animStar_2{
	from {
		transform: translateY(-1000px);
	}
	to {
		transform: translateY(1000px);
	}
}

Die Animation des Raumschiff ist ähnlich wie bei den Sternen umgesetzt. Die Keyframes teilen wir dieses Mal in Prozente ein.

#spaceship{
	animation-delay: 4s;
	animation: futurama 10s linear forwards;
}

@keyframes futurama{
	0% {
		max-width: 1%; 
		transform: translateX(-20em);
	}
	25% {
		max-width: 4%; 
	}
	100% {
		transform: translateX(-10em);
		max-width: 14%; 
	}
}

Zweite Ebene

HTML

Für die zweite Ebene, also für die Datei game.html , fügen wir im Head-Bereich zusätzlich zu GSAP ein Script von LottieFiles hinzu . Dieser ermöglicht später eine Lottie-Animation zu starten.

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.7.0/gsap.min.js"></script> <!--Green Sock-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.7.0/MotionPathPlugin.min.js"></script> <!--Green Sock-->		 

<script src="https://unpkg.com/@lottiefiles/lottie-player@latest/dist/lottie-player.js"></script> <!-- Lottiefiles-->

Der HTML-Code ist auch hier unkompliziert gestaltet. Der Div mit der class=”grid” ist der wichtigste Bestandteil dieses HTML-Codes. Über ihn erzeugt die app.js-Datei die Memory-Karten .

Bei der Lottie-Animation handelt es sich um eine Konfetti-Animation, die nur beim Gewinnen des Spiels über Javascript aktiviert wird. Sie ist in der HTML-Datei über den <lottie-player>-Tag eingebunden und über CSS angepasst.

<!-- Memory-Spiel -->
    <div class="grid"></div> <!-- Memorykarten werden über app.js erzeugt -->   

    <img id="gameAstro" src="Bilder/astro.png"/>        <!-- Astronaut -->
    <img id='Traene' src='Bilder/water.svg'> <!-- erscheint, wenn verloren -->
    <div id="score" >
        <h3>
            Du hast 30 Sekunden Zeit <br/> 
            Dein Score:<span id="result"></span>
        </h3>
        <p id="message"></p>
    </div>


<!--Spiel gewonnen-->
<lottie-player src="https://assets6.lottiefiles.com/packages/lf20_u4yrau.json" id='confetti' background="transparent"  speed="1.15"    loop  autoplay></lottie-player>
<div id='gewonnen'><h1>6/6! Gut gemacht!</h1></div>


<!--Spiel verloren-->    
<div id="Schande"><p>Schade!</p></div>
<div id='verloren'><h1>Verloren</h1></div>


<!-- "Neues Spiel"  Button -->  
<a href="javascript:location.reload()"><div id="nochMal">Neues Spiel</div></a>
Javascript

Der grundlegende JS-Code für das Memory-Spiel stammt aus diesem YouTube-Video. Der Code wurde modifiziert und an eigene Bedürfnisse angepasst.

Das Programm führt folgende Schritte aus: createBoard() kreiert innerhalb des .grid-Divs Child-Elemete, die für die Erstellung der Karten benötigt werden. Timer löst nach 30 Sekunden die Funktion verloren() aus, die die Elemente .grid und und scoreDiv entfernt und stattdessen andere Elemente aktiviert. Die Funktion checkForMatches() erfüllt gleich mehrere Aufgaben: Sie vergleicht die angeklickten Karten und je nach Auswahl gibt einen Text in der Sprechblase aus. Wenn sie feststellt, dass alle Paare gefunden wurden, löscht bzw. deaktiviert sie bestimmte Elemente und blendet neue ein. Die flipCard()-Funktion sorgt dafür, dass die Karten “umgedreht” werden und man das verborgene Bild sieht. Es können nur zwei Karten gleichzeitig umgedreht werden.

document.addEventListener('DOMContentLoaded', () => {

  //Kartenauswahl
  
  const cardArray = [
    {
      name: 'mond',
	  img: 'Bilder/mond.svg'
    },
    {
      name: 'alien',
	  img: 'Bilder/alien.svg'
    },
    {
      name: 'ufo',
	  img: 'Bilder/ufo.svg'
    },
    {
      name: 'rocket',
	  img: 'Bilder/rocket.svg'
    },
    {
      name: 'space',
      img: 'Bilder/space.svg'
    },
    {
      name: 'sun',
      img: 'Bilder/sun.svg'
    },
    {
      name: 'mond',
      img: 'Bilder/mond.svg'
    },
    {
      name: 'alien',
      img: 'Bilder/alien.svg'
    },
    {
      name: 'ufo',
      img: 'Bilder/ufo.svg'
    },
    {
      name: 'rocket',
      img: 'Bilder/rocket.svg'
    },
    {
      name: 'space',
      img: 'Bilder/space.svg'
    },
    {
      name: 'sun',
      img: 'Bilder/sun.svg'
    }
  ];

  cardArray.sort( () => 0.5 - Math.random());
  

  const grid = document.querySelector('.grid');
  const resultDisplay = document.querySelector('#result');
  const wordCloud = document.querySelector('#message');
  const scoreDiv = document.querySelector('#score');
  const gameAstro = document.querySelector('#gameAstro');
  
  let cardsChosen = [];
  let cardsChosenId = [];
  let cardsWon = [];
  
  //create your board

  function createBoard() {
    for (let i = 0; i < cardArray.length; i++) {
	const cardDiv = document.createElement('div') //Um die karten herum einen Div erstellen
	cardDiv.setAttribute('class', 'imgDiv') //Um die karten herum einen Div erstellen
	
    const card = document.createElement('img')
	card.setAttribute('class', 'Spielbilder')
    card.setAttribute('src', 'Bilder/logo.svg')
    card.setAttribute('data-id', i)
    card.addEventListener('click', flipCard)
	  
	grid.appendChild(cardDiv).appendChild(card) //Um die Karten herum einen Div erstellen
    }
  };
  
    
  //Timer. Falls das Spiel mach 30 Sekunden nicht gewonnen wurde, wird das Spiel abgebrochen
  	var timer = setTimeout( function(){verloren();}, 30000);
	function verloren(){
	  grid.remove()
	  scoreDiv.remove()
	  gameAstro.style.marginTop = '17em'
	  gameAstro.style.marginLeft = '7em'
	  gameAstro.style.width = '10em'
	  
	  document.querySelector('#verloren').style.display = 'block'
	  document.querySelector('#Schande').style.display = 'block'
	    
	  gsap.timeline().from("#gameAstro", {duration: 2, y: 200, opacity: 0, scale: 0, rotation: 180, ease:'back'});
	  
	  document.querySelector('#Traene').style.display = 'block'
	};
  

  //check for matches
  function checkForMatch() {
    const cards = document.querySelectorAll('img')
    const optionOneId = cardsChosenId[0]
    const optionTwoId = cardsChosenId[1]
	
    
    if(optionOneId == optionTwoId) {
      cards[optionOneId].setAttribute('src', 'Bilder/logo.svg')
      cards[optionTwoId].setAttribute('src', 'Bilder/logo.svg')
	  wordCloud.textContent = 'Du hast die gleiche Karte angeklickt!'
	  setTimeout(function(){ wordCloud.innerHTML=''; }, 2000); //Nach 2000 ms den text aus der Sprechblase entfernen
    }
    else if (cardsChosen[0] === cardsChosen[1]) {
	  wordCloud.textContent = 'Du hast ein Paar gefunden!'
	  setTimeout(function(){ wordCloud.innerHTML=''; }, 2000); //Nach 2000 ms den text aus der Sprechblase entfernen
      cards[optionOneId].removeAttribute('src', 'Bilder/logo.svg')
      cards[optionTwoId].removeAttribute('src', 'Bilder/logo.svg')
      cards[optionOneId].removeEventListener('click', flipCard)
      cards[optionTwoId].removeEventListener('click', flipCard)
      cardsWon.push(cardsChosen)
    } else {
      cards[optionOneId].setAttribute('src', 'Bilder/logo.svg')
      cards[optionTwoId].setAttribute('src', 'Bilder/logo.svg')
	  wordCloud.textContent = 'Schade, versuch es noch mal'
	  setTimeout(function(){ wordCloud.innerHTML=''; }, 2000); //Nach 2000 ms den text aus der Sprechblase entfernen
    }
    cardsChosen = []
    cardsChosenId = []
    resultDisplay.textContent = cardsWon.length
	var audio = new Audio('ta-da.mp3');
    if  (cardsWon.length === cardArray.length/2) {
      //Falls das Spiel innerhalb von 30 Sekunden gewonnen:
	  clearTimeout(timer)  //Timer aussetzen, damit das Spiel nicht abgebrochen wird
      audio.play();
	  grid.remove()
	  scoreDiv.remove()
	  gameAstro.style.marginTop = '17em'
	  gameAstro.style.marginLeft = '7em'
	  gameAstro.style.width = '10em'
	  
	  document.querySelector('img#gameAstro').style.animationName = 'none' //Animation entfernen, damit GreenSock-Animation abgespilt werden kann
	  document.querySelector('#gewonnen').style.display = 'block'
	  document.querySelector('#confetti').style.display = 'block'
	  
	  //Freudiges Springen
	  gsap.timeline().from("#gameAstro", {duration: 2, y: 200, opacity: 0, scale: 0, rotation: 180, ease:'back'})
					.to('#gameAstro', { duration: .25, y: -50,  repeat: -1, yoyo: true, ease: "sine.inOut", autoRound: false}); 
    }
  };

  //flip your card
  function flipCard() {
	
    let cardId = this.getAttribute('data-id')
    cardsChosen.push(cardArray[cardId].name)
    cardsChosenId.push(cardId)
    this.setAttribute('src', cardArray[cardId].img)
    if (cardsChosen.length === 2) {
      setTimeout(checkForMatch, 300)
    }
  };

	createBoard();
});
css

Das Aussehen der Karten wird über die Klassen .grid , .imgDiv und .Spielbilder bestimmt. Die Klasse .grid ist für die Verteilung der Karten auf dem Bildschirm zuständig. Hier empfielt es sich, die Einstellung display:flex zu wählen. Es ist der leichteste Weg ist, die Karten zu positionieren. Für die richtige Reihung der Karten ist es wichtig, dass die Breite des .grid-Containers und die Breite der einzelnen Karten, die von .imgDiv gesteuert werden, auf einander abgestimmt sind.

/* Während des Spiels*/

.grid{
	display: flex;
	flex-wrap: wrap;
	width: 30em;
	margin-right: 5em;
	margin-top: 3em;
	float:right;

}

.imgDiv {
	background-color: rgba(204, 102, 255, 0.55);
	border-radius: .25em;
	width:7em;
	height:12em;
	margin:.25em;
	box-shadow: rgba(50, 50, 93, 0.65) 0px 50px 100px -20px, rgba(0, 0, 0, 0.6) 0px 30px 60px -30px, rgba(10, 37, 64, 0.65) 0px -2px 6px 0px inset;	
}
.imgDiv:hover {
	box-shadow: rgba(153, 255, 102, 0.65) 0px 50px 100px -20px, rgba(0, 0, 0, 0.6) 0px 30px 60px -30px, rgba(153, 255, 102, 0.65) 0px -2px 6px 0px inset;	
}
img.Spielbilder { 
	max-height:100%; 
	max-width:100%;
	padding:1em;
}

Dritte Ebene

Die einzelnen Elemente der dritten Ebene werden je nach Ergebnis des Spieles durch den JS-Code aktiviert. Aus diesem Grund sind sie alle mit display:none versehen. Der JS-Code verändert sie dann mit document.querySelector zu display:block.

/* Spiel gewonnen*/
h1 {
	font-size: 5em;
}

#gewonnen {
     animation: Schweben 5s linear easy-in;
    display: none;
    position: absolute;
    left: 53em;
    top: 20em;
    z-index: 10;
}
#confetti{
	display: none;
	width:75em;
	height: auto;
	float: right;
}
/* Spiel verloren*/
#verloren {
	display: none;
	animation: Schweben 6s linear infinite;
    position: absolute;
    left: 53em;
    top: 20em;
    z-index: 10;
	float:left;
}
#Schande {
	display: none;
	animation: Schweben 13s linear infinite;
	clip-path: polygon(0% 0%, 100% 0%, 100% 75%, 69% 75%, 5% 100%, 27% 76%, 0% 75%);
	width: 16em;
	height:9em;
	margin-top: 13em;
	margin-left:4em;
	padding: 1em;
	float: left;	
	background-color: rgba(204, 102, 255, 0.55);
}
#Schande p {
	font-size: 2em;
	text-align: center;
}

#Traene {
	display: none;
	width: 0em;
	position:absolute;
    left: 12.5em;
	top: 22em;
	z-index: 15;
	animation: traene 3s linear infinite;
	transition: ease-in;
	animation-delay: 3s;
}
@keyframes traene{
	from {
		transform: translateY(0em);
		width: 0.5em;
	}
	to {
		transform: translateY(13em);
		width: 3em;

	}
	to {
		transform: translateY(15em);
	}
	to {
		transform: translateY(16em);
		opacity: 60%;
	}
	to {
		transform: translateY(17em);
		opacity: 7%;
	}
}

Sowohl in der zweiten als auch in der dritten Ebene werden alle sichtbaren Elemente (bis auf die Memory-Karten) mit einer Schwebeanimation versehen, um den Eindruck zu vermitteln, dass die Objekte wie im echten Weltall schweben.

/*Alles soll schweben*/
@keyframes Schweben {
	0% {

		transform: translatey(0px);
	}
	25%{
		transform: translatex(-5px);
	}
	50% {

		transform: translatey(-10px);
	}
	100% {

		transform: translatey(0px);
	}

}

Dieser Beitrag ist im Studiengang Informationsmanagement an der Hochschule Hannover im Rahmen des Kurses Entwicklung von Multimediasystemen (Sommersemester 2021, Amy Linh Hoang,  Prof. Dr.-Ing. Steinberg) entstanden. Verwendete Techniken sind HTML5, CSS3 und JavaScript. Die besten Tutorials stellen wir Euch hier in den nächsten Wochen nach und nach vor.

Tutorial: Ein Tag mit Bobby

Beitragsbild Tutorial zur Animation: Ein Tag mit Bobby

Autor*in: Julia Solohub 


Animation anschauen

Quellcode (Zip-Archiv zum Download)

Dieser Beitrag ist ein Tutorial zu der Animation „Ein Tag mit Bobby“ bei dem auf die Details zum Code, Inhalt, Besonderheiten, das Konzept und Schwierigkeiten eingegangen wird. Die Bilder dienen zur Veranschaulichung und Nachvollziehbarkeit und sind anklickbar, sodass man diese in Vollbildmodus und besserer Auflösung betrachten kann.

Inhaltsverzeichnis

Das Konzept

Die Animation ist ein Storytelling, bei dem der User mit Hilfe von Buttonklicks durch die einzelnen Szenen geleitet wird.  Dieser begleitet den Hauptcharakter Bobby durch den Tag und führt verschiedene Anweisungen durch, die Bobby in der Sprechblase gibt. Diese können durch einen Buttonklick realisiert werden. Sobald der User auf einen Button klickt, wird entweder die Szene gewechselt oder ein visueller Effekt erzeugt.

Das Ziel ist es mit dem User eine Interaktion zu starten, wobei dieser den Ablauf der Animation lenkt. Der User wird zu Beginn auch direkt von Bobby mit dem Namen angesprochen und aufgefordert mit ihm den Tag zu verbringen.

Die Idee eine Animation mit Hund(en) zu erstellen, kommt von der persönlichen Liebe zu Tieren und insbesondere zu Hunden. Die kindliche und amüsante Darstellung soll den User animieren und erfreuen. Die Hintergrundmusik sorgt für eine zusätzliche entspannte Atmosphäre, die die Stimmung der Animation unterstützt. Der User kann sich somit entspannen, zurücklehnen und die Animation genießen.

Aufbau

Die Animation ist in vier Szenen aufgeteilt:

  1. Start-Szene (div class=“ story_start_scene“)
  2. Bobby im Wohnzimmer (div-class= „dog_in_livingroom_scene“)
  3. Bobby im Park (div class=”background_image_park_day”)
  4. Schluss-Szene bei der Bobby im Park nachts ist (div class=”dog_in_park_night”)

Start Szene

Bei der Start Szene sieht man den schlafenden Bobby auf einem Teppich ohne jeglichen Hintergrund, das sich zu den restlichen Szenen unterscheidet. Der Grund dafür ist, dass man zum einen den beweglichen und farbwechselnden Hintergrund auf sich wirken lassen kann und zum anderen sollte es schlicht gehalten werden, damit der Wechsel zu den Hauptszenen spannender gestaltet wird. Zudem soll der Fokus zu Beginn auf dem Hauptcharakter liegen. Mit dem Buttonklick auf „Aufwecken“ startet die ganze Animation.

Start Szene

Zweite Szene

In der zweiten Szene befindet sich Bobby im Wohnzimmer, wo er zuvor auf dem Teppich geschlafen hat. Der User hat ihn aufgeweckt und nun fragt Bobby ihn nach seinem Namen. Die Abfrage passiert zwischen der ersten und der zweiten Szene, die mit Hilfe von JavaScript erzeugt wurde. Je nachdem welchen Namen der User eingibt, erscheint dieser in dem Begrüßungstext in der Sprechblase. Hier wird der User aufgefordert die Anweisungen von Bobby durchzuführen. In dieser Szene soll der User mit Bobby in den Park gehen.

Zweite Szene
Zweite Szene

Dritte Szene

Die dritte Szene spielt sich im Park tagsüber ab in den mehrere Aktionen passieren. Zu Beginn sieht man den Park, in dem sich ein Grill und Bobby befinden. Bobby weist darauf hin, dass es vom Grill sehr gut riecht und er gerne einen Hot Dog haben möchte. Dieser Bitte kann der User nachgehen, indem er auf den langsam erscheinenden Button „Hot Dog geben“ klicken kann. Sobald dies gemacht wurde, erscheint ein animierter Hot Dog, der vom Grill aus zu Bobby in einer 360 Grad Drehung geworfen wird. Daraufhin bedankt sich Bobby und gibt den nächsten Hinweis, dass er seinen Freund Bello sieht und mit ihm Ball spielen möchte.

Dritte Szene
Dritte Szene

Auch hier kann der User der Anweisung nachgehen, indem er auf den nächsterscheinenden Button „Mit Bello spielen“ klickt. Hier erfolgt die nächste Animation, bei der sich Bello und Bobby den Ball zu werfen. Dabei springen beide versetzt hoch und werfen den Ball mit ihrem Kopf einander zu. Nun erscheint der fünfte Button „Weiter“, der den User darauf hinweist, dass danach die nächste Szene erscheint.

Vierte Szene

Vierte Szene
Vierte Szene

Die vierte und letzte Szene findet in dem selben Park statt. Jedoch ist es schon spät geworden und Bobby ist müde. Er macht vor Freude Rückwärtssaltos und bedankt sich für den großartigen Tag, den er mit dem User erlebt hat. Auch hier weist Bobby daraufhin, dass er müde und erschöpft ist. Hier ist es Zeit sich von Bobby zu verabschieden und ihm eine Gute Nacht zu wünschen.

Letzte Szene
Letzte Szene

Mit dem Buttonklick kann man dies tun, wobei das Bild von Bobby in den ursprünglich schlafenden Zustand zurückgebracht wird. Es taucht ein Eyecatcher auf, der zoomt und sich hin und her in verschiedene Winkel dreht. Dies ist der Abschluss der Animation, der durch den animierten Text „Gute Nacht, Bobby“ kenntlich gemacht wird. Der letzte Button unterscheidet sich in der Erscheinungsweise von allen anderen, denn dieser macht auf sich Aufmerksam, indem sich dieser hin und her bewegt und dabei vertikal spiegelt. Der Grund für diese Erscheinungsweise ist das Aufmerksam machen, dass ab hier keine neue Szene erscheint, sondern alles nochmal vom Anfang beginnt. Somit ermöglicht dieser Button die Animation nochmal neu zu starten

Der Code

Für die Animationen wurden HTML5, CSS3 sowie JavaScript und jQuery benutzt. HTML5 wurde für das Grundgerüst und Aufbau, CSS3 für die Animation sowie das Styling und JavaScript bzw. jQuery für das Auslösen/Verstecken verschiedener Animationsobjekte benutzt.

Allgemeine Einstellungen

In der ID „animation_start“ in HTML5 spielt sich die gesamte Animation ab. Die jeweiligen Szenen sind in einzelne div-classes unterteilt in denen die dazugehörigen Objekte enthalten sind. Die div-classes sind nach den Szenarien benannt, das das Erkennen der Inhalte vereinfacht.

<div id="animation_start"class="whole_animation"> <!--ID der gesamten Animation-->
      <div class="story_start_scene"><!--Erste Szene: Startbildschrim-->
         <img src="sleeping_dog.png" id="sleep"/>
         <img src="carpet.png" id="carpet"/>
       </div>
    
       <div class="dog_in_livingroom_scene"> <!---Zweite Szene: Bobby im Wohnzimmer Szene samt Begrüßung-->
          <div class="living_room_background">
            <img src="room.png" class="backgroundimage_livingroom" style="visibility: hidden;"/>
           </div> 
           <div class="dog_character">
             <img src="standing_dog.png" id="hello_dog" style="visibility: hidden;"/>
             <img src="sprechblase.png" id="speak1" style="visibility: hidden;"/> 
             <p id="welcome_text" style="visibility: hidden;">Hallo! Ich bin Bobby. Lass uns den Tag zusammen verbringen.</p>
           </div>
       </div>

       <div class="dog_in_park_day"> <!--Dritte Szene: Bobby im Park Szene mit dazugehörigen Objekten-->
           <div class="background_image_park_day">
             <img src="meadow_at_day.png" class="backgroundimage_park_day" style="visibility: hidden;"/>        
           </div>
           <div class="park_items">
             <img src="grill.png" id="bbq" style="visibility: hidden;"/>
             <img src="food.png" id="hot_dog" style="visibility: hidden;"/>
             <img src="standing_dog2.png" id="dog_at_park" style="visibility: hidden;"/>
             <img src="sprechblase2.png" id="speak2" style="visibility: hidden;"/>
             <p id="hot_dog_text" style="visibility: hidden;">Das riecht aber toll! Kannst du mir einen Hot Dog geben?</p>
             <p id="thank_you_hot_dog_text" style="visibility: hidden;">Danke! Ouh Schau! Da ist Bello. Ich möchte mit ihm Ball spielen.</p>
             <img src="playing_dog_brown.png" id="bello" style="visibility: hidden;"/>
             <img src="playing_dog_grey.png" id="bobby" style= "visibility: hidden;"/>
             <img src="ball.png" id="toy" style="visibility: hidden;"/>    
           </div> 
       </div>

       <div class="dog_in_park_night"><!--Abschlussszene: Bobby im Park nachts.--> 
           <div class="background_image_park_night">
             <img src="meadow_at_night.png" id="backgroundimage2" style="visibility: hidden;"/> 
             <img src="dog_by_night.png" id="night_dog" style="visibility: hidden;"/>
             <img src="sprechblase3.png" id="speak4" style="visibility: hidden;"/>
             <img src="sleeping_dog2.png" id="tired" style="visibility: hidden;"/>  
             <p id="thank_you_for_the_day" style="visibility: hidden;">Danke dir für den schönen Tag! Ich bin jetzt aber müde.</p>
             <p id="good_night_bobby" style="visibility: hidden;">Gute Nacht, Bobby</p>
           </div>
       </div>
 </div><!--Ende der "animation_start"-->

In der aside-class wurden alle Buttons und der Titel der der Animation eingetragen, da diese Inhalte sich immer an der selben Position befinden.

<body>
  <!--Alle Elemente sind auf "visibility:hidden" gesetzt, die über JavaScript aktiviert werden, wenn man auf einen Button klickt-->
  <script src="animation_functions.js"></script> <!--Einbindung der JavaScript Datei-->
  <aside class="title_and_buttons"> <!--Übergreifende Class für alle Buttons und Titel der Animation (h1) damit die Position die selbe bleibt-->
    <h1> Ein Tag mit Bobby</h1>
    <button id="button_aufwecken">
       <a class="button_1">Aufwecken</a>
    </button>
    <button id="button_park" style="visibility: hidden;">
      <a class="button_2">Lass uns in den Park gehen</a>
   </button>
   <button id="button_hot_dog" style="visibility: hidden;">
      <a class="button_3">Hot Dog geben </a>
    </button>
    <button id="button_play" style="visibility: hidden;">
      <a class="button_4">Mit Bello spielen</a>
    </button>
    <button id="button_next" style="visibility: hidden;">
     <a class="button_5">Weiter</a>
   </button>
   <button id="button_good_night" style="visibility: hidden;">
     <a class="button_6">Gute Nacht!</a>
  </button>
   <button id="button_reload" style="visibility: hidden;">
     <a class="button_7">Neustart</a>
 </button>
</aside> 

Auffallend im Code ist, dass alle Objekte nicht sichtbar sind (visibility: hidden). Diese werden zu dem Zeitpunkt wann die jeweiligen Objekte zu sehen sein sollen mit Hilfe von jQuery sichtbar gemacht. Die allgemeine Position aller Elemente ist in der ID „animation“ sowie dessen class „whole_animation“ zu sehen. So entsteht ein Rahmen für die verschiedenen Bilder, das die Zentrierung der Szenen möglich macht. Die gewählte Größe und Position erstellt eine Box, wo sich die Animation abspielt. Dies soll ein Gefühl erzeugen, als würde man in einem Kino vor einer Leinwand sitzen.

#animation { /*Ausrichtung der gesamten Animation*/
  align-items: center;
  min-height: 100%;
  position: absolute;
}

.whole_animation {
  max-width: 900px;
  margin: 0em auto 0;
  text-align: center;
  max-height:800px;  
  overflow: hidden;
}
  
.title_and_buttons { /*Ausrichtung aller Buttons und der h1*/
  max-width: 900px;
  text-align: center;
  margin: 1em auto; 
  padding:1px;
}
   
h1 {
  color: white;
  font-size: 1.7rem;
}

Im Body ist die Animation des ineinanderfließenden Hintergrundes zu sehen. Die keyframes bringen diese Animation zum Laufen. Zunächst muss man drei Farben definieren, die man in dem Übergang haben möchte. Des Weiteren war es wichtig die Dauer und Länge zu definieren. In diesem Fall sollte der gesamte Übergang 13 Sekunden dauern und unbegrenzt durchlaufen.

body {
  font-family: "Comic sans MS"; 
  background-size: cover; 
  justify-content:center;
  background-position: center;
  background: -webkit-linear-gradient(132deg, #FC415A, #591BC5, #4a8ead); 
  background-size: 400% 400%;
  align-items: center;
  min-height: 100%;
  overflow:hidden;
  -webkit-animation: moving_background 13s ease infinite;
  animation: moving_background 13s ease infinite;
}

@-webkit-keyframes moving_background {  /*Animation des Backgrounds, der in Dauerschleife die Farben wechselt*/
  0% {
      background-position: 0% 50%;
  }
  50% {
      background-position: 100% 50%; /*Inspired by https://www.sliderrevolution.com/resources/css-animated-background/*/
  }
  100% {
      background-position: 0% 50%;
  }
}

Die Buttons

Die Buttons haben wie schon zuvor erwähnt den funktionalen Zweck den User durch die Szenen zu geleiten und verschiedene Animationen hervorzurufen. Somit sind diese essenziel für das Abspielen der Animation. Aus diesem Grund werden diese oberhalb der Animationsbox dargestellt, damit die Aufmerksamkeit des Users zu den bestimmten Zeitpunkten darauf gelenkt wird.

Die Buttons haben alle den selben Stil, jedoch haben alle verschiedene Dauer und Art und Weise der Animation. Daher wurde jeder einzelne Button in CSS3 angelegt. Das Aussehen der Buttons ist Oval und hat drei Farbverläufe, das in zarten und hellen Farben gehalten ist, um mit dem Hintergrund zu harmonieren. Beim Hovern erfolgt ein Spiegeleffekt bei dem die Farben von rechts nach links ineinander laufen. Durch das bewegte Gelb wir ein Relief- bzw. Spiegeleffekt erzeugt. Die transition ermöglicht dies.

#button_aufwecken { /*Design und Animation der Buttons. Alle haben die gleiche Einstellung nur andere Animationsangaben. Daher jeden Button einzeln angelegt, anstatt die zusammenzufassen*/
  border-radius: 50%;
  align-items: center;
  font-size: 15px;
  display: flex;
  margin: auto;
  background-image: linear-gradient(135deg, #ee9ca7 0%, #fbed96 51%, #ee9ca7 100%);  /*Inspired by https://blog.avada.io/css/button-hover-effects/*/
  background-size: 200% auto;
  box-shadow: #a05a9c 0px 0px 5px;
  -webkit-animation: push_buttom 3s infinite;
  animation: push_button 3s infinite;
  outline: none;
}


@-webkit-keyframes push_button {                       /*Inspired by https://css-tricks.com/css-animation-libraries/*/
  0% {
    box-shadow: 0px 0px 4px rgb(255,250,250);
  }
          
  
  50% {
    box-shadow: 0px 0px 23px rgb(255,250,250);
  
  }
  100% {
    box-shadow: 0px 0px 12px rgb(255,250,250);
  }
}

@keyframes push_button {                       
0% {
  box-shadow: 0px 0px 4px rgb(255,250,250);
}
        

50% {
  box-shadow: 0px 0px 23px rgb(255,250,250);

}
100% {
  box-shadow: 0px 0px 12px rgb(255,250,250);
 }
} 

#button_aufwecken:hover {
  background-position: right center;
  transition: all 0.5s ease-out;
  }
      
.button_1{
  flex: 1 auto;
  margin:5px;
  padding:10px 25px;
  text-align:center;
  transition: all 0.5s ease-out;
  cursor:pointer;
  background-size: 200% auto;
  font-family: "Comic sans MS";
}

Animierte Objekte

Alle benutzen Bilder, bis auf die Hintergrundbilder der Szenen, haben einen funktionalen Zweck. Alle Bilder sind PNG Dateien und von Pixabay. Durch die position: relativ war es einfach alle Objekte in einer Szene zu platzieren. Ausgerichtet und an den gewünschten Platz gebracht wurde mit bottom, top, left, right und margin.  Keyframes erzeugen die Animationen der Objekte. Die nachfolgenden Abschnitte beschreiben die einzelnen animierten Objekte.

Text in Sprechblase

Der sprechende Text, den Bobby in der Sprechblase sagt zoomt rein und raus und wechselt dabei zwischen drei Farben. So sollte die Aufmerksamkeit auf den Text gelenkt werden, damit der User den Anweisungen folgen kann. Dazu wird die animation-timing-function auf ease gesetzt. Zudem soll der Zoomeffekt in Dauerschleife ablaufen. Daher der Wert infinite. Durch scale() konnte die Größe der Transformation des Textes eingerichtet werden. Color sorgt für den Farbverlauf, der sich über die verschiedenen Sequenzen (0%, 50%, 100%) ändert.

#welcome_text { /*Annimation für Text in der Sprechblase. Wird in jeder Szene eingesetzt*/
  position: relative;
  bottom: 392px;
  left: 475px;
  z-index: 4;
  font-size: 10px;
  box-sizing: border-box;  
  overflow: hidden;
  border-width: 20px;
  border: 1px hidden;
  width: 135px;

  -webkit-animation: typing_hello 1.5s ease infinite; 
  -webkit-animation-delay: 0s;
  -webkit-animation-play-state: paused;
  -webkit-animation-fill-mode: forwards;

  animation: typing_hello 1.5s ease infinite;
  animation-delay: 0s;
  animation-fill-mode: forwards;
  animation-play-state: paused;     
}

  @-webkit-keyframes typing_hello {
   0% {
    transform: 
       scale(0.7, 0.7);
       color:rgb(116, 9, 107)
  }
   50% {
    transform: 
       scale(0.9, 0.9);
       color:rgb(85, 25, 134)
  }
   100% {
    transform: 
      scale(0.7, 0.7);
      color:rgb(73, 41, 214)
  }
}

Fliegender Hot Dog

Für die Animation des fliegenden Hot Dogs wurden in den keyframes die Werte translateX(), translateY() und rotate() verwendet. Die beiden ersten Werte sorgen für die Verschiebung auf der X- und Y-Aschse, wobei ein negativer translateY() Wert das Objekt nach oben versetzt. Daher ist bei 50%, das der höchste Punkt der Flugbahn sein soll, ein negativer Wert von -80px. rotate() sorgt für die Rotation des Objektes. Hierbei müssen Gradzahlen eingetragen werden.

#hot_dog { /*Animation des fliegenden Hot-Dogs*/
  width: 47px;
  z-index: 2;
  position: relative;
  bottom: 300px;
  right: 225px;

  -webkit-animation-name: flying_hot_dog;
  -webkit-animation-duration: 2s;
  -webkit-animation-timing-function: linear;
  -webkit-animation-fill-mode: forwards;
  -webkit-animation-delay: 0s;
  -webkit-animation-iteration-count: 1;
  -webkit-animation-play-state: paused;
  
  animation-name: flying_hot_dog; 
  animation-duration: 2s;
  animation-fill-mode:forwards;
  animation-delay: 0s;
  animation-timing-function: linear;
  animation-iteration-count: 1;
  animation-play-state: paused;
}
 
  @-webkit-keyframes flying_hot_dog {
    from  { transform: translateX(0px) translateY(20px)  rotate(0deg) }
    50%   { transform: translateX(220px) translateY(-80px) rotate(180deg) }
    to    { transform: translateX(440px) translateY(95px)rotate(360deg) }
  }

  @keyframes flying_hot_dog {
    from  { transform: translateX(0px) translateY(20px)  rotate(0deg) }
    50%   { transform: translateX(220px) translateY(-80px) rotate(180deg) }
    to    { transform: translateX(440px) translateY(95px)rotate(360deg) }
  }

Bello und Bobby beim Ballspielen

Die Animation der zwei spielenden Hunde war am schwierigsten zu gestalten, da hier der Zeitfaktor eine große Rolle spielt, damit alles synchron abläuft. Zunächst die Gestaltung der beiden springenden Hunde. Hierbei muss man die Sequenzen (0%-100%) der bottom-Wert ändern. Je größer der Wert, desto höher steigen die Objekte auf. Die ease-out Eigenschaft sorgt dafür, dass das Ende der Sequenz langsamer abläuft. So kommt der fließende Sprung zustande. Beide Hunde haben die selben Animationseigenschaften. Der Unterschied liegt im animation-delay. Bello fängt eine Sekunde später an zu springen als Bobby. So entsteht der abwechselnde Sprung.

#bello { /*Ab hier folgt die Animation der zwei mit dem Ball spielenden Hunde*/
  width: 90px;
  z-index: 3;
  position: relative;
  bottom: 430px;

  -webkit-animation: move_bello 2s infinite;
  -webkit-animation-delay: 1s;

  animation: move_bello 2s infinite;
  animation-delay: 1s;
}

@-webkit-keyframes move_bello {
  from { bottom: 477px; animation-timing-function: ease-out; }
  50% { bottom: 570px; animation-timing-function: ease-out; }
   to { bottom: 477px; }
}

@keyframes move_bello {
  from { bottom: 477px; animation-timing-function: ease-out; }
  50% { bottom: 570px; animation-timing-function: ease-out; }
   to { bottom: 477px; }
}

#bobby {
  width: 90px;
  z-index: 3;
  position: relative;
  left: 200px;
  bottom: 430px;

  -webkit-animation: move_bobby 2s infinite;
  animation: move_bobby 2s infinite;;
}

@-webkit-keyframes move_bobby {
  from {  bottom: 477px; animation-timing-function: ease-out; }
    50% { bottom: 570px; animation-timing-function: ease-out; }
     to { bottom: 477px; }
}

Die Animation des Balls wurde nach dem selben Konzept wie die des Hot Dogs gestaltet. Hier bestand die Schwierigkeit mit der Positionierung und der Dauer des fliegenden Balls. Es musste alles auf die beiden springenden Hunde angepasst werden, sodass der Effekt eines Kopfballs entsteht.

Saltomachender Bobby

In der letzten Szene macht Bobby ein Rückwärtssalto. Dieser Effekt wurde mit den gleichen Eigenschaften wie bei dem fliegenden Hot Dog und Ball animiert. Da der Charakter sich auf einer Stelle bewegt und nur hochspringt und dreht, bleibt der translateX()vWert bei 0px. Dem entsprechend muss der translateY() und rotate() angepasst werden.

#night_dog {
  width: 80px;
  position: relative;
  right: 380px;
  bottom: 1039px;
  -webkit-animation: salto_dog 2s infinite;
  -webkit-animation-timing-function: linear;
  animation: salto_dog 2s infinite;
  animation-timing-function: linear;
}

@-webkit-keyframes salto_dog {  /*Animation von Bobby, der eine Rückwärtsrolle macht*/
  from  { transform: translateX(0px) translateY(0px)  rotate(0deg) }
  50%   { transform: translateX(0px) translateY(-80px) rotate(-180deg) }
  to    { transform: translateX(0px) translateY(0px)rotate(-360deg) }
}

Buttons

#button_reload {
border-radius: 50%;
align-items: center;
font-size: 15px;
display: flex;
margin: auto;
background-image: linear-gradient(135deg, #ee9ca7 0%, #fbed96 51%, #ee9ca7 100%); /*https://blog.avada.io/css/button-hover-effects/*/
background-size: 200% auto;
box-shadow: #a05a9c 0px 0px 5px; 
margin-top: -50px;
}

#button_reload {
-webkit-animation-name: button_load;
-webkit-animation-duration: 3s;
-webkit-animation-timing-function: linear;
-webkit-animation-fill-mode: forwards;
-webkit-animation-iteration-count: infinite;
-webkit-animation-play-state: paused;
-webkit-animation-direction: alternate-reverse;

animation-name: button_load; 
animation-duration: 3s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-iteration-count:infinite;
animation-play-state: paused;
animation-direction: alternate-reverse;
}

@-webkit-keyframes button_load {
  from  { transform: translateX(-20px) scaleX(1.0)}
  to    { transform: translateX(20px) scaleX(-1.0)}
} 

Die Buttons wurden im vorherigen Kapitel schon beschrieben. Nun wird näher auf die animierenden Effekte eingegangen. Der Effekt des Einblendens wurde durch die opacity Eigenschaft erzeugt, der von 0 auf 1 steigt und durch die animation-duration in die Länge gezogen wird. Der erste und letzte Button haben jedoch ein paar zusätzliche Effekte. Beim ersten Button „Aufwecken“ leuchtet der Button-Hintergrund in Weiß. Durch den versetzten box-shadow und der Dauerschleife (infinite) wird das pulsierende Leuchten erzeugt (siehe Bild ). Der letzte Button „Neustart“ dreht und bewegt sich von links nach rechts und rückwärtsrum, während sich dieser vertikal spiegelt. Die animation-direction: alternate-reverse gibt an, dass die Animation sich vor- und zurückspielen soll. Animation-timing-function sorgt für den linearen. also gleichbleibenden/flüssigen Ablauf. translateX() sorgt dafür, dass sich der Button von links nach rechts bewegt. scaleX() hingegen sorgt für die Spiegelung, wobei der negative Wert die Spiegelung an der X-Achse erzeugt.

Gute Nacht, Bobby Schrift

Der Abschließende Text „Gute Nacht, Bobby“ wurde mit einer Zoom-, Leucht- und Rotations-Funktion gestaltet. Wie schon zuvor erwähnt erzeugt scale() den Zoomeffekt. Für das Textleuchten ist text-shadow zuständig. rotate() sorgt für die Drehung des Textes. Der Unterschied bei dieser Textanimation liegt in der animation-timing-function. Hierbei setzt man cubic-bezier () ein um, einen Verzerrungseffekt zu erstellen.

#good_night_bobby{ /*Verabschiedung durch "Gute Nacht" Text Animation*/
  position: relative;
  bottom: 1500px;
  font-size: 30px;
  color: white;
  -webkit-animation: push_good_night 3s infinite;
  animation: push_good_night 3s infinite;
  animation-timing-function: cubic-bezier(.1,0,.7,1);
  outline: none;
}

  @-webkit-keyframes push_good_night {
   0% {
    transform: scale(1);
    text-shadow: 2px 3px 6px rgb(3, 246, 255);
  }
   20% {
    transform: scale(1.1) rotate(2deg);
  }                                                  
   40% {
    transform: scale(.97);
  }
   50% {
    text-shadow: 6px 6px 8px rgb(19, 236, 184);
 }
   60% {
    transform: scale(.99) rotate(-2deg);
 }
   80% {
    transform: scale(1.05);
 }
   100% {
    transform: scale(1);
    text-shadow: 2px 3px 6px rgb(135, 222, 241);
 }
 }

  @keyframes push_good_night {
   0% {
    transform: scale(1);
    text-shadow: 2px 3px 6px rgb(3, 246, 255);
  }
   20% {
   transform: scale(1.1) rotate(2deg);
  }                                                  
   40% {
   transform: scale(.97);
  }
   50% {
   text-shadow: 6px 6px 8px rgb(19, 236, 184);
}
   60% {
   transform: scale(.99) rotate(-2deg);
}
   80% {
   transform: scale(1.05);
}
   100% {
   transform: scale(1);
   text-shadow: 2px 3px 6px rgb(135, 222, 241);
}
}

JavaScript/jQuery

var name = "Snoopy" /*Variable für die Namensabfrage für die Begrüßung von Bobby*/

$(document).ready(function(){
/*Funktion, bei der bei einem Buttonklick bestimmte Objekte sichtbar oder ausgeblendet werden*/
$("#button_aufwecken").on("click", function() {  /*Button "Aufwecken"*/
    $(".story_start_scene").css({"visibility": "hidden"});
    $(".backgroundimage_livingroom").css({"visibility": "visible"}); 
    $("#hello_dog").css({"visibility": "visible"});
    $("#speak1").css({"visibility": "visible"});
    $("#button_park").css({"visibility": "visible"});
    $("#button_aufwecken").css({"visibility": "hidden"})
    $("#welcome_text").css({"visibility": "visible"});
    $("#name_text").css({"visibility": "visible"});
    $("form").css({"visibility": "visible"});
    
    $("#button_park").css({"-webkit-animation-play-state": "running"}); /*Animationen, die pausiert werden, werden hierbei zum Abspielen gebracht*/
    $("#welcome_text").css({"-webkit-animation-play-state": "running"});

    name = prompt("Bitte gib deinen Namen ein", "Snoopy");     /*Namensabfrage. Inspired by https://www.w3schools.com/jsref/met_win_prompt.asp*/
    console.log(name);

    if (name != null) {                                                                    
        document.getElementById("welcome_text").innerHTML =
        "Hallo " + name + "! Ich bin Bobby. Lass uns den Tag zusammen verbringen.";
      }

});

Um die Übergänge zwischen den einzelnen Szenen und Animationen möglich zu machen, wurde jQuery eingesetzt. Durch den Buttonklick werden Objekte, die in HTML5 auf „hidden“ gesetzt sind, sichtbar gemacht. Dafür nutzt man die $(“#button”).on(“click”, function() {}}); . Diese zeigt welche Objekte beim Buttonklick gezeigt werden und welche verschwinden sollen. Die Namensabfrage zwischen der ersten und der zweiten Szene erfolgte über JavaScript mit der promt() Methode.

Bobby JS
Bobby JS

Hierbei wurde eine Variable erstellt, das einen default Wert liefert, der in der promp() Abfrage und if-loop eingefügt ist. Es öffnet sich ein Pop-up Fenster bei dem die Aufforderung „Bitte gib deinen Namen ein“ und dem default Wert „Snoopy“ steht. Diese wurden zuvor definiert. Wenn der User seinen Namen eintippt, nimmt die p id=“welcome_text“ diesen entgegen und Bobby begrüßt den User mit „Hallo [Username]! Ich bin Bobby. Lass und den Tag zusammen verbringen“. Falls das Eingabefeld leer bleibt, begrüßt Bobby den User mit dem Namen null. Durch diese Funktion wird eine Bindung zum User aufgebaut.

Die Animationen, die mit Hilfe von transitions und keyframes in CSS3 erstellt wurden, wurden bei webkit-animation-play-state auf paused gesetzt. Der Grund dafür ist, dass bestimmte Funktionen wie animation-delay oder animation-duration ab der Start Szene anfangen zu zählen. Das war aber nicht die Intention. Es sollten alle Abläufe erst nach einem Buttonklick starten. So war das Problem z.B. bei dem zweiten Button „Lass und in den Park gehen“. Dieser wird nach 3 Sekunden eingeblendet. Wäre -webkit-animation-play-state auf running gesetzt, so hätte das Einblenden des Buttons nach 3 Sekunden nicht funktioniert, wenn man sich länger als 3 Sekunden in der Start Szene befunden hätte. Der Button wäre dann beim Szenenwechsel schon da und der Einblende Effekt wäre nicht sichtbar. Damit die einzelnen Animationen erst beim Buttonklick abspielen, muss man diese über jQuery bei -webkit-animation-play-state auf running setzen.

$("#hot_dog_text").css({"-webkit-animation-play-state": "running"});
$("#button_hot_dog").css({"-webkit-animation-play-state": "running"});

Besonderheiten

Eine Besonderheit ist die im Hintergrund eingesetzte Musik, um die Animation spannender zu gestalten und den User in eine angenehme Stimmung zu versetzen. Des Weiteren wurden so gut wie alle benutzen Objekte animiert. Es wurde versucht jedes Animationselement individuell zu gestalten, um verschiedene visuelle Effekte zu erzeugen. Die letzte Besonderheit ist, dass der letzte Button einen Neustart der Animation ermöglicht, falls man erneut das Story Telling durchspielen möchte.

Schwierigkeiten

Beim Erstellen der Animation traten zwei Probleme auf.

  1. Der animierte Sprechtext sollte ursprünglich abgetippt werden. Dafür wurde die step() Methode verwendet. Leider konnte ein Zeilenumbruch für die Animation nicht erstellt werden, da ansonsten der “abgetippte Text” nicht korrekt funktioniert hat wie er es sollte. Dieser hätte in die Sprechblase passen müssen. Daher wurde eine alternative Darstellung des Textes eingebaut.
  2. Die Hintergrundmusik wird manchmal in Google Chrome nicht abgespielt. Daher wird empfohlen sich die Animation in Microsoft Edge anzuschauen.

Dieser Beitrag ist im Studiengang Informationsmanagement an der Hochschule Hannover im Rahmen des Kurses Entwicklung von Multimediasystemen (Sommersemester 2021, Amy Linh Hoang,  Prof. Dr.-Ing. Steinberg) entstanden. Verwendete Techniken sind HTML5, CSS3 und JavaScript. Die besten Tutorials stellen wir Euch hier in den nächsten Wochen nach und nach vor.

Tutorial: Visual Graphic Novel Animation

Tutorial: Visual Graphic Novel Animation

Autori*n: Julia Karaschewski


Vollansicht: Visual Graphic Novel Animation auf Codepen.io
Quellcodeansicht: Visual Graphic Novel Animation auf Codepen.io
ZIP-Datei zum Download (direkt von Codepen.io)

Das Konzept

Die Idee der Animation ist, ein erheiterndes, kleines Visual Graphic Novel zu erstellen, durch das der User mit visuellen Effekten durchgeführt wird.

Das Ziel ist, den User von Anfang bis Ende durch mehrere kleine Animationen zu führen, während er Interaktionen ausführt und Feedback zu den ausgeführten Aktionen erhält. Das Feedback besteht ebenfalls aus visuellen Effekten, sodass der User jederzeit weiß, wo und in welcher Ebene er sich befindet.

Die Führung durch das Visual Graphic Novel erfolgt visuell durch verschieden aufleuchtende Buttons. Durch sich farblich (türkis) abgrenzende Umrandungen der Buttons wird der User zu den gewünschten Aktionen gelotst, da diese am meisten auffallen (über Bewegung und Farbe).

Das Visual Graphic Novel besteht aus zwei Szenen, die der Nutzer frei wählen kann. Die Reihenfolge der Auswahl hat dabei keine Auswirkungen.

Inhaltsverzeichnis des Beitrags

Aufbau des Visual Graphic Novels

Es gibt 4 Ebenen:

  • 1. Start-Bildschirm
  • 2. Szenen-Wahl
  • 3. Home-Scene
  • 3. Outdoor-Scene
  • 4. End-Bildschirm

Die erste Ebene, der Startbildschirm, dient dem User dazu, das Visual Graphic Novel zu starten.
Auf der zweiten Ebene kann der User eine Szene (Home- oder Outdoor-Szene) auswählen, ohne jedoch das Szenenbild vorher zu sehen. Hier liest er nur den Text der Entscheidungsmöglichkeiten, was auch einen kleinen Überraschungseffekt bei der Auswahl geben soll.
Auf der dritten Ebene kann der User dann zwischen den beiden Szenen hin und her springen, so oft er möchte. Um das ganze unterhaltsamer zu machen, spricht die Figur einen Text. Die Sprechblase wiederholt sich hierbei unendlich oft, sodass der User den Text gut lesen kann, ungeachtete der Lesegeschwindigkeit.
Die vierte Ebene ist der Endbildschirm, wo sich ein “Thanks for playing” (DE: “Danke für das Spielen”) bei dem User verabschiedet. Auf dieser Ebene hat der User die Möglichkeit, das Visual Graphic Novel erneut zu starten.

Verwendete Programmiersprachen:

Die ganze Animation wurde auf Codepen erstellt. Für die Erstellung der einzelnen Animation/-en wurden verwendet:

    • HTML5
    • CSS3
    • JavaScript & JQuery

Um JQuery zu verwenden, muss dies bei den Codepen-Einstellungen erst mit diesem Link https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js importiert werden, wie auf der folgenden Abbildung:

Eigene Abbildung: Bei Codepen JQuery importieren
Eigene Abbildung: Bei Codepen JQuery importieren

Besonderheiten

Die Besonderheiten bei dem Visual Graphic Novel sind klein, haben jedoch einen großen Effekt und Einfluss.
Zunächst ist das Design schlicht gehalten, bzw. der Hintergrund und die Hauptfarbe der Buttons. Dadurch wird die Aufmerksamkeit des Users auf die Szenen(-bilder) und die einzelnen, visuellen Effekte gelenkt.

Die Effekte der Klasse ‘hint

Die Klasse ‘hint’ hat das Ziel, den User durch das Visual Graphic Novel zu führen. Der visuelle Effekt ist ein türkis-farbenes Pulsieren (in CSS definiert). Beim Startbildschirm (Ebene 1) wird die Aufmerksamkeit des Users somit vor allem auf den Start-Button gelenkt. Danach (Ebene 2) wird der Effekt “verschoben” und ist nun nur auf der Szenen-Auswahl zu sehen, damit der User weiß, dass er sich nun für eine der beiden Optionen entscheiden soll. Ist nun eine Szene ausgewählt (Ebene 3), wird der pulsierende Farb-Effekt auf die nicht ausgewählte Option verschoben, sowie den Stop-Button. Damit weiß der User zum Einen, dass die andere Option noch ausgewählt werden kann, und zum Anderen, dass jederzeit das Spiel nun beendet werden kann. Wird nun auf den Stop-Button geklickt, wird der Farb-Effekt erneut auf den Start-Button geschoben, damit der User nun die Möglichkeit erkennt, erneut auf Start zu klicken. Die Steuerung, wann der Farb-Effekt, bzw. die Klasse ‘hint’ wo hin verschoben werden soll, wird mit Hilfe der Buttons in JavaScript festgelegt. Der Effekt selbst wird mit CSS erstellt, mehr dazu unter dem Abschnitt Visuelle Effekte.

Die Figur und Objekte

Die Figur ist nicht nur eine Dekoration, sondern erfüllt auch einen Zweck: Ein Feedback an den User. Das Feedback ist simpel: Wurde sich nun für eine Szene entschieden, taucht eine Sprechblase von der Figur auf. Damit wird dem User gezeigt, dass die Szenen-Auswahl durchgeführt wurde und etwas passiert ist. Dabei wird der gesprochene Text in der Sprechblase unendlich oft wiederholt, damit der User jederzeit den Text erneut und so oft er will lesen kann. Des Weiteren erheitert der gesprochene Text der Figur (hoffentlich!) den User und hinterlässt somit einen positiven Eindruck. Die Figur wird in CSS mit der Klasse ‘figure’ definiert.
Die anderen Objekte / Graphiken dienen hier der Dekoration und erhöhen den Spaß-Effekt beim Umschalten der Szenen.

Der Wegweiser

Der Wegweiser (eine SVG-Graphik), der einzig allein in Ebene 2 auftaucht, besitzt eine wichtige Funktion: Das Wegweisen. Da Graphiken, bzw. Bilder, meist mehr für sich sprechen als Text, wird dem User vor dem Lesen der Optionen bereits gezeigt, dass nun eine Entscheidung auf ihn zukommt. Ein Wegweiser ist hierbei ein allgemein bekanntes Symbol, dass von dem User nicht falsch verstanden werden kann, und dabei schneller auffällt als der Text in den Optionen. Der Wegweiser wird mit der ID ‘#signpost’ in CSS definiert und mit Hilfe von JavaScript gesteuert. Mehr dazu unter dem Abschnitt Buttons & Option.

Grundbausteine

Da es in dem Visual Graphic Novel viele verschiedene Elemente gibt, die von JavaScript gesteuert werden und auf verschiedenen Instanzen auftauchen sollen, schließen verschiedene div-Container diese ein.

Body

body {
background-size:cover;
font-family: "Comic Sans MS", Helvetica, sans-serif;
border: 1px dashed #ffffff;
border-radius: 30%;
box-shadow:0px 0px 350px; #000000;;
background: radial-gradient(#28427B,#14213D, #0d001f);
justify-content:center;
background-position: center;

Im ‘body’ wird alles Wichtige wie background-size, background-color, sowie die Inhaltsplatzierung innerhalb des ‘bodys’ und die Schriftart definiert. Die border- und box-shadow-Einstellungen schaffen hierbei eine Abgrenzung von dem restlichen Browserfenster und begrenzen die Darstellungsoberfläche des Visual Graphic Novels.

Main-Content-Container

.main-content {
max-width: 700 px;
margin: 0em auto 0;
text-align: center;
max-height:500px;
overflow: hidden;

Der div-Container mit der Klasse ‘main-content’ ist dafür da, um den gesamten Inhalt (also alle Graphiken) als eine Einheit zu platzieren und sie durch JavaScript und JQuery zu steuern, d.h. einen Start-Bildschirm zu erschaffen. Damit kann der gesamte Inhalt in ‘main-content‘ mit einem Klick aus- und eingeblendet werden, wenn einer der Buttons gedrückt wird. Das geschieht dadurch, dass dieser Klasse eine Id ‘gamestop’ und ‘gamestart’ (definiert in CSS) mit der Eigenschaft ‘visibility:hidden’ zugewiesen und wieder entfernt wird.

<!-- die Id wird in JS genutzt, um einen Startbildschirm zu erschaffen --!>
<div id="gamestart" class="main-content"> <!-- wird genutzt, um den main-content zu steuern und zu positionieren -->
$(".start-button").on("click", function() {
$(".main-content").removeAttr("id", "gamestart");
$(".stop-button").on("click", function() {
$(".main-content").attr("id", "gamestop");

 

Mehr zu den Buttons später.

Illustration-Container

.illustration {
position: relative;
align-self: flex-end;
marfin: auto;
height: 250px;
width: 250px;

Der div-Container ‘illustration’ enthält alle Graphiken, bzw. SVG-Graphiken, und fixiert diese. Da nicht alle SVG-Graphiken eine geeignete Proportionalität besitzen, um zusammen dargestellt zu werden, hilft das Fixieren auch dabei, um sie gegenseitig aufeinander abzustimmen.

Person-type: Home-Body & Outdoor-Person

<div class="person-type home-body active"> <!-- hier ist die home-scene mit dazugehörigen Objekten -->
<div class="scenery">
<img src="https://image.freepik.com/free-vector/living-room-interior-with-panoramic-window-night-time_33099-1735.jpg" class="backgroundimg"/>
</div>
<div class="background.items">
<img src="https://image.flaticon.com/icons/svg/214/214283.svg" width="100px" height="400px"/>
</div>
<div class="foreground-items">
>img src="http://image.flatcon.com/icons/svg/198/198916.svg" width="65px" style="margin-left:-10%"/>
<img src="Http://image.flaticon.com/icons/svg/3145/3145435.svg" width="65px" style="padding-left:30%;"/>
</div>
</div>

Mit der Klasse ‘person-type‘ werden in CSS alle Kindelemente dieser Klasse definiert und in JavaScript zwischen den Szenen hin- und hergewechselt, wenn die jeweilige Option ausgewählt wird.

.person-type > div {
transform: scale(0);
opacity:0;
}
.person-type.active > div {
transform: scale(1);
opacity: 1;

Durch ‘person-type > div’ werden alle Kindelemente der Klasse ‘person-type‘ ausgewählt. Es gibt ‘person-type‘ einmal für die Home-Szene (inklusive der dazugehörigen Graphiken) und dann für die Outdoor-Szene. Wenn eine der Szenen ausgewählt wird, wird diese Szene durch ‘person-type.active’ (definiert in CSS) nach “vorne geschoben‘(transform:scale (1))’ und durch ‘opacity:1’ sichtbar gemacht. Die andere Szene wird durch ‘person-type > div’ nach “hinten geschoben ‘(transform:scale(0))’ und mit ‘opacity:0’ unsichtbar gemacht. Dadurch können beide Szenen an den gleichen Platz im Container geschoben werden, ohne dass sie sich gegenseitig verdrängen. Die Szenenauswahl wird durch den Button mit JavaScript ausgelöst. Darauf wird später in Buttons & Option noch mehr eingegangen.

Container Scenery & Backgroundimg

Der div-Container ‘scenery’ umfasst das Szenenbild (für jeweils die Home- und Outdoor-Szene). Mit dieser Klasse wird der Einblendungseffekt / Übergangseffekt beim Szenenwechsel in CSS mit ‘transition:’ definiert.

<div class="scenery">
<img src="https://image.freepik.com/free-vector/living-room-interior-with-panoramic-window-night-time_33099-1735.jpg" class="backgroundimg"/>
</div>

Der div-Container ‘backgroundimg’ wird in CSS definiert und erfüllt den Zweck, die Hintergrundbilder der Szenen auf die gleiche Größe zu skalieren und dem Bild mit ‘border-radius’ und ‘box-shadow’ eine Form und einen visuellen Tiefen-Effekt zu geben.

.scenery 7
transition: all 200ms cubic-bezier(0.68, -0.55, 0.265, 1.55);
z-index: -2;
0/* Styled die Hintergrundbilder der Scenen */
.backgroundimg7
border-radius:50%;
margin-left:-73%;
width:626px;
height: 3356px;
box-shadow: 0px 0px 15px #000000;

Foreground-items Container

<div class="foreground-items">
<img src="https://image.flaticon.com/icons/svg/198/198916.svg" width="65px" style="margin-left:-10%;"/>
<img src="https://image.flaticon.com/icons/svg/3145/3145435.svg" width="65px" style="padding-left:30%;"/>
</div>

Der ‘foreground-items’ div-Container beinhaltet die Graphiken, die sich in den beiden Szenen jeweils weiter vorne befinden und bei einem Szenenwechsel später eingeblendet werden als die Graphik im Hintergrund und das Hintergrundbild.

.forground-items {
transition: all 300ms cubuic-bezier(0.68, -0,55, 0.265, 1.55) 200ms;
margin-top:100%;
}

Mit ‘transition:’ wird der Einblendungseffekt / Übergangseffekt dieser Objekte beim Szenenwechsel festgelegt.

Background-items Container

Der div-Container mit der Klasse “background-items” beinhaltet die eine Graphik (jeweils in beiden Szenen), die zu einem früheren Zeitpunkt als die Graphiken in dem ‘foreground-items container’ in der ausgewählten Szene auftauchen. Dies ist in CSS mit ‘transition:’ definiert, wie in der folgenden Abbildung zu sehen ist.

.backgrouznd-items {
transition: all 200ms cubic-bezier(0.68, -0.55, 0.265, 1.55) 100ms;
top: 23px;
}

Option-Wrapper

Der div-Container ‘option-wrapper’ beinhaltet beide Szenen-Optionen und umschließt diese, damit sie zusammen in einem Button erscheinen können. Außerdem wird mit dieser Klasse der div-Container per JavaScript “anvisiert”, bzw. mit einem Leuchteffekt versehen, damit dieser zum geeigneten Augenblick die Aufmerksamkeit des Users bekommt. Zu den Buttons unter Buttons und zu dem Leucht-Effekt unter Visuelle Effekte mehr.

<div class="option-wrapper">
<aclass="option active wobble-vertical-on-hover homeoption hint" data-option="home">
I'm gonna read a nice book and stay in. </a>
<a class="option wobble-vertical-on-hover outdooroption hint" data-option="outdoor">
I'm gonna explore the wide world and beyond! </a>
</div>

Buttons & Option

Die Klasse ‘option’ ist allen Buttons zugeteilt. Also jeweils dem Start- und Stop-Button sowie beiden Szenen-Optionen. Die Klasse ‘option’ regelt mit ‘transition:’ in CSS den Übergang der ausgewählten Option sowie den Style der einzelnen Optionen. Somit grenzen sich z.B. die Outdoor-Szene und Home-Szene innerhalb des ‘Option-Wrappers’ voneinander ab und erscheinen als zwei eigene Buttons. Mit ‘cursor’ wird zudem der Cursor über dem Button definiert.
Die Klasse ‘active’ definiert, wie die ausgewählte Option aussieht. Durch die Klasse ‘active’ verändert sich die Hintergrundfarbe und Schriftfarbe des Buttons (gelber Hintergrund, schwarze Schrift), wenn dieser angeklickt wird.

.option {
transition: all 200ms ease;
padding: 10px 10px 8px;
width: 50%;
border-radius: 50px;
cursor: pointer;
color: #ffffff;
} /* gibt aktiver Option style */
.active{
background: #FFB200;
pointer-events: none;
color: '000000;
}
.option:hover: {
text-decoration: none;
}

Bei der Szenenauswahl wird der ‘person-type’ der jeweiligen Option angesprochen, der bei beiden Szenen unterschiedlich ist und in der JavaScript-Funktion jeweils definiert ist, bzw. welche Elemente dazugehören. Der Wechsel der Optionen, sowie die Zuweisung der Klasse ‘active’, geschieht mit folgender Funktion in JavaScript:

$(".option").on("click", function() {
$(".person-type").removeClass(active");
$(".option").removeClass("active");
$(this).addClass("active");
var type = $(this).data("option");
console.log($(type));

Die beiden Person-Types (‘data-option’ in HTML) der Optionen, also ‘person-type home’ und ‘person-type outdoor’ in JavaScript, bestimmen, welche weiteren Grafiken / Objekte dargestellt werden. Dies ist definiert als if und else if (untere Abbildung) in der Funktion “click”, (obere Abbildung). Wird also die ‘option’ mit dem ‘type == “home”‘ (in HTML data-option) angeklickt, wird der Klasse ‘home-body’ die Klasse ‘active’ zugewiesen. Desweiteren werden damit alle benötigten Elemente für diese Szene eingeblendet, sowie nicht benötigte ausgeblendet, mit folgender Funktion:

setTimeout(function() {
if (type === "home") {
$(".home-body").addClass("active");
$("#signpost").css({"visibility": "hidden"});
$("#bubbletoggle1").css({"visibility": "visible"});
// Hier der Wechsel der Sprachblasen
$(".bubble2").hidden();
$(".bubble1").show();
$".stop-button").addClass("hint");
$(".homeoption").removeClass("hint");
}
<div class="option-wrapper ">
<a class="option active wobble-vertical-on-hover homeoption hint" data-option="home">
I'm gonna read a nice book and stay in.</a>
<a class="option wobble-vertical-on-hover outdooroption hint" data-option="outdoor">
I'm gonna explore the wide world and beyond! </a>
</div>

Der ‘Signpost’ ist eine Grafik, die in Ebene 2 eingeblendet wird, hier jedoch ausgeblendet werden soll. Mit der Klasse ‘hint’ wird eine farbliche (türkis-farbene) Umrandung um den Stop-Button gesetzt, damit der User darauf aufmerksam gemacht wird, dass er das Visual Graphic Novel ab jetzt beenden kann. Gleichzeitig wird diese Klasse der jetzt ausgewählten Option entzogen, da die Aufmerksamkeit des Users bereits auf diesem Button liegt. Die ID ‘#bubbletoggle1’ ist in einem anderen Abschnitt erklärt.

Der Start- und Stop-Button

Der Start- und Stop-Button dient dazu, einen Start-Bildschirm und einen End-Bildschirm zu erschaffen, sowie dem User die Möglichkeit zu geben, dass Visual Graphic Novel jederzeit zu verlassen. Dazu wird den beiden Buttons die Klasse ‘active’ gegeben, damit diese genauso wie die Szenen-Options reagieren (weiter oben beschrieben). Ebenso wird zum Zwecke der User-Lenkung und User-Aufmerksamkeit die Klasse ‘hint’ vergeben, die farbliche Effekte (türkis-farbene Umrandung) vergibt.

<span class="hint">
<div class="button hint" style="">
<a class="Start-button option wobble-veretical-on-hover hint">Star !</a>
<a class="stop-button option wobble-vertical-on-hover">Stop playing !</a>
</div>
</span>

Der Style der beiden Buttons wird in CSS festgelegt. Dafür wird die Klasse ‘buttons’ vergeben. Der Zweck der Klasse ‘hint’ wird im Abschnitt ‘Besonderheiten’ und die CSS-Definition unter Visuelle Effekte  mehr erläutert.

.button{
display: flex;
position: center;
margin:0en auto;
background: rgba(255,255,255,0.25);
border-radius: 50px;
padding: 5px;
max-width: 300px;
font-size: 15px;
line-height: 1.2;
} /* Wird genutzt, um per JS zu steuern, wann der Inhalt auftaucht, wenn die buttons gedrückt werden*/
#gamestart{
visibility:hidden;
}
#gamestop{
visibility:hidden;
}/* Danke-Text am Ende des Spiels */

Die ID ‘#gamestart{visibility:hidden;}’ und ‘#gamestop{visibility:hidden;}’ werden in JavaScript dafür verwendet, um beim Klicken der Buttons den ‘main-content’ einzublenden und beim Drücken des Stop-Buttons den ‘main-content’ auszublenden. Zur Übersichtlichkeit der Steuerung in JavaScript wurden also die beiden ID’s jeweils einzeln in CSS definiert, obwohl sie die gleichen Attributwerte besitzen. Die beiden Funktionen für den Start-Button und den Stop-Button sehen wie folgt aus:

$(".star-button").on("click", function() {
$(".main-content").removeAttr("id", "gamestart");
$(".thanks").css({"visibility": "hidden"});
$("#signpost").css({"visibility": "visible"});
// "hint" gibt dem User Hinweise, wo hingeklickt werde
$(".button").removeClass("hint");
$(".start-button").removeClass("hint");
$(".option-wrapper").addClass("hint");
});
//Funktion und eizelne Aktionen, die ausgeführt werde
$(".stop-button").on("click", function() {
$(".mnain-content").attr("id", "gamestop");
$(".bubble").hide();
$("#signpost").removeAttr("style", "visible");
$(".thanks").css({"visibility": "visible"});
$(".button").addClass("hint");
// wenn der stop-button geklickt wird, muss das lightni
$(".stop-button").removeClass("hint");
//hiermit wird die Klasse "hint" den beiden Scenen-Opt
$(".homeoption").addClass("hint");
$(":outdooroption").addClass("hint");
});

Die beiden Funktionen definieren auch, was beim Drücken des Buttons eingeblendet und ausgeblendet werden soll. Dies geschieht durch die Klassen und ID’s, die in CSS definiert sind (wenige Ausnahmen befinden sich als Attributwert in HTML).

Sprechblase

Die Sprechblase der Figur in beiden Szenen hat jeweils einen verschiedenen Text. Aus diesem Grund wurden einmal die Klassen ‘bubble1’, ‘bubbletoggle1’, ‘bubble2’, ‘bubbletoggle2’ vergeben und an beide jeweils die Klassen ‘bubble’ und ‘anim-typewriter’.

<div id="bubbletoggle1" class="buble1" style="visibility:hidden;">
<a lass="bubble bubble1 anim-typewriter "> Ah! That was a good desicion! Now a nice cup of tea...would you set the kettle on? </a>
</div>
<div id="bubbletoggle2" class="bubble2" style="visibilioty:hidden;">
<a class="bubble bubble2 anim-typewriter">What a lovely day, isn't it? Ans such charming strangeers visiting!</a>
</div>

Die Klasse bubble

Die Klasse ‘bubble’ ist dafür da, um die Sprechblase per CSS zu stylen. Die CSS-Attributwerte von ‘min-width’, ‘max-width’, ‘max-height’ sind hierbei besonders wichtig, da sie bestimmen, wie groß die Sprechblase werden kann, wenn der Text auftaucht. Je nachdem, wie lang der Text ist, muss natürlich auch die ‘min-width’, ‘max-width’ und ‘max-height’ angepasst werden. Wäre ‘max-height’ in diesem Fall kleiner als die festgelegten ’50px’, würde nicht der ganze Text auftauchen können.

.bubble {
z-index:4;
min-width: 0px;
max-width: 210px;
max-height: 50px;
font-size: 15px;
line-height: 1.2;
display: flex;
text-align: center;
position: absolute;
border-radius: 50px;
margin-right:auto;
margin-left: auto;
left: 85%;
righht:0;
top:50%;
padding:1px;

Des Weiteren ist die Klasse ‘bubble’ auch dafür da, um mit dem Stop-Button die Sprechblase an sich vollends auszublenden (siehe Abschnitt Buttons.

Die Klasse anim-typewriter

Diese Klasse ist in CSS definiert. Sie sorgt für den Effekt der auftauchenden Sprechblase, wo der Text nach und nach erscheint.

.anim-typewriter{
animation: typing 7s steps(8,end) intinite normal both;
-webkit-animation-duration: 7s;
}
@keyframes typing{
from{width: 0;}
to{width: 20em;}

Wichtig hierbei ist der Attributwert infinite, der leider etwas schwierig zu handhaben ist. Darauf wird in “bubbletoggle: Der unendliche Text” weiter eingegangen.

Die Klassen bubble1 und bubble2

Die Klassen ‘bubble1’ und ‘bubble2’ werden in JavaScript verwendet, um die jeweilige Sprechblase in der richtigen Szene einzublenden und auszublenden. Die Klasse ‘bubble1’ ist hierbei für die Home-Szene, ‘bubble2’ für die Outdoor-Szene. Aus- und eingeblendet wird die Klasse in JavaScript mit ‘$(.bubble1).hide()’ und ‘$(.bubble1).show()’. Da beide Szenen auch unterschiedlich helle Hintergründe haben, wird mit dieser Klasse in CSS auch die Farbe der Sprechblasen angepasst.

.bubble.bubble1{
background:rgba(255, 255, 255, 0.30);
padding: 6px 6px 4px;
}
.bubble.bubble2{
background:rgba(26, 22, 95, 0.55);
padding: 6px 6px 4px;

Bubbletoggle: Der unendliche Text

Eigene Abbildung: Der unendliche Text der Sprechblase
Eigene Abbildung: Der unendliche Text der Sprechblase

Der Text in den Sprechblasen wird unendlich wiederholt, um dem User das Lesen zu ermöglichen und zu vereinfachen, wann auch immer die Szene ausgewählt wird und unabhängig davon, wie schnell die Lesegeschwindigkeit des Users ist. Die Klassen ‘bubbletoggle1’ und ‘bubbletoggle2’ werden dafür verwendet, um den Text der Sprechblasen durch JavaScript (und CSS) in HTML mit ‘visibility:hidden’ und ‘visibility:visible’ unsichtbar und sichtbar zu machen. Dies ist sehr ähnlich wie das, was die Klassen ‘bubble’ und ‘bubble1’ (oder bubble2) schon machen. Hierbei gibt es aber ein Problem: Unendlich heißt unendlich. Die einzige Möglichkeit in diesem Fall, den sich unendlich wiederholenden Text in der Sprechblase unsichtbar zu machen, ist über eine extra ID und ein dazugehöriger Style-Attributwert in HTML, der in JavaScript gezielt angesprochen wird. Damit wird keine andere Klasse beeinflusst oder verändert, was später zu Komplikationen führen könnte. In JavaScript sieht das dann folgendermaßen aus: $(“bubbletoggle1″).css({visibility:”visible”}); für die Home-Szene und für die Outdoor-Szene $(“#bubbletoggle2”).css({“visibility:”visible”}); . In dem Abschnitt Buttons ist die gesamte Funktion erklärt und zu sehen.

<div id="bubbletoggle1" class="bubble1" style="visibility:hidden;">
<a class="bubble bubble1 anim-typewriter ">Ah! That was a good decision! Now a nice cup of tea...would you set the kettle on?</a>
</div>
<div id="bubbletoggle2" class="bubble2" style="visibility:hidden;">
<a class="bubble bubble2 anim-typewriter">What a lovely day, isn't it? And such charming strangers visiting!</a>
</div>

Visuelle Effekte

Die Klasse hint

Eigene Abbildung: Klasse hint beim Start-Button und Stop-Button
Eigene Abbildung: Klasse hint beim Start-Button und Stop-Button

Die User-Lenkung durch die Klasse ‘hint’ wurde vom Zweck und der Funktion her im Abschnitt Besonderheiten erläutert. Der Licht-Pulse-Effekt wird in CSS erstellt und definiert.

.hint{
-moz-border-radius: 200px/200px;
-webkit-border-radius: 200px 200px;
border-radius: 200px/ 200px;;
border:1px solid #52c7aa;
width: auto;
height:auto;
outline:none;
-webkit-animation-name: hintPulse;
-webkit-animation-duration: 2s;
-webkit-animation-interation-count: infinite;
}
@-webkit-keyframe hintPulse {
from { -webkit-box-shadow: 0 0 3px #0c2720; }
50% { -webkit-box-shado: 0 0 21px #52c7aa; }
to {-webkit-box-shadow: 0 0 12px #246E5B; }
}

Die Dauer der Animation ist mit ‘-webkit-animation-duration:2s;’ festgelegt. Der Farbwechsel (oder auch die Intensität) im Verlauf der Animation ist durch die ‘@-webkit-keyframes hintpulse’ definiert. Dabei wurden verschiedene Abstufungen des original Farbtons für die Intensität verwendet.

Der Wobble-Effekt

Der Wobble-Effekt wird durch die Klasse ‘wobble-vertical-on-hover’ in CSS definiert. Beim Hovern über ein Element mit dieser Klasse “wobbelt” jenes. Damit weiß der User, dass mit diesem Element interagiert werden kann. Die Definition und Animation ist in CSS festgelegt.

.wobble-vertical-on-hover:hover, .wobble-vertical-on-hover:focus, .wobble-verical-on-hover:active {
-webkit-animation-name: wobble-vertical-on-hover;
animation-name: wobble-vertical-on-hover;
-webkit-animation-duration: 1s;
animation-duration: 1.5s;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: 1;
-webkit-animation-interation-count: 1;
animation-interation-count: infinite;
} /* Bewegungen der Animation wobble, aufgeteilt nach Prozent */
@keyframes wobble-vertical-on-hover {
16.65% {-webkit-transform: translateY(8px);transform: translateY(8px);}
33.3% {-webkit-transform: translateY(-6px);transform: translateY(-6px);}
49.95% {-webkit-transform: translateY(4px);transform: translateY(4px);}
66.6% {-webkit-transform: translateY(-2px);transform: translateY(-2px);}
83.25% {-webkit-transform: translateY(1px);transform: translateY(1px);}
100% {-webkit-transform: translateY(0);transform: translateY(0);}
}

Mit der Klasse ‘wobble-vertical-on-hover’ und zusätzlich ‘:hover’, ‘:focus’, ‘:active’ wird festgelegt, dass sowohl beim hover, als auch so lange die Maus darauf ist der Effekt gilt. Mit ‘animation-iteration-count:infinite’ wird dieser Effekt so lange wiederholt, bis weder ‘hover’,’active’ noch ‘focus’ für dieses Element gilt, bzw. es dadurch nicht mehr ausgelöst wird. Mit ‘@keyframes wobble-vertical-on-hover’ wird die Bewegung nach oben und unten definiert/ festgelegt; also die Vektoren, wo sich das Element mit dieser Klasse an einem bestimmten Prozent(-punkt) der Bewegung befinden muss.

Und nun sind alle wichtigen Code-Abschnitte erklärt. Falls das Interesse an dem Quellcode (inklusive Kommentare darin!) vorhanden ist, muss nur wieder nach zum Seitenanfang gesprungen, die ZIP-Datei heruntergeladen und entpackt werden!


Dieser Beitrag ist im Studiengang Informationsmanagement an der Hochschule Hannover im Rahmen des Kurses Entwicklung von Multimediasystemen (Sommersemester 2020, Amy Linh Hoang,  Prof. Dr.-Ing. Steinberg) entstanden. Verwendete Techniken sind HTML5, CSS3 und JavaScript.

Die besten Tutorials stellen wir Euch hier in den nächsten Wochen nach und nach vor.

Videos rundum IM: Das Client-Server-Modell

WebLab HsH: Client-Server-Modell

Das Client-Server-Modell

Autor: Kirill Prokopov

Inhalt:

In der Animation erkläre ich die Rollen von Client und Server. Ebenso wird die Kommunikation zwischen beiden Geräten grob erläutert.

Software:

Verwendet habe ich hierzu die Software „Blender“ (für Animationen, das Compositing und Videoschnitt) und den freien Software Synthesizer „Synth1“ für das Einspielen von Audio.

Die „Hauptdarsteller“ sind dabei Piktogramme von Client und Server und animierte Textelemente, die deren Funktion erklären und verdeutlichen. Die Piktogramme und die Texte liegen auf verschiedenen Ebenen und sind somit im Compositing von „Blender“ separat mit Effekten animierbar.

Realisiert im Compositing sind die Unschärfe- und Helligkeitsanimationen der Piktogramme sowie die Farbwechselanimation des Hintergrundverlaufs. Dies erfolgt simultan zu den erklärenden Texten und deren Keyframeanimationen. Ich setzte Compositing und Ebenen vor allem ein, damit die Rechenzeit pro Bild in einem vertretbaren Rahmen von 15 Sekunden pro Bild bleibt. Ohne Compositing und Ebenen wären es ca. 2 Minuten pro Bild gewesen, was bei knapp 1500 Bildern nicht so schön gewesen wäre.

 Ablauf in Blender:

  • Programm berechnet die Ebene mit den Piktogrammen
  • Programm berechnet die Ebene mit den Texten
  • im Compositing wird der Hintergrundverlauf hinzugefügt und dessen Farbe nach Position in der Animation angepasst
  • über den Hintergrund wird die Ebene mit den Piktogrammen gelegt und je nach Position in der Animation Unschärfe und Helligkeit verändert
  • darüber wird letztendlich die Ebene mit den Texten gelegt und das Endergebnis als Bild abgespeichert
  • dies geschieht Bild für Bild bis die Animation (1481 Bilder) abgearbeitet und alle Einzelbilder abgespeichert sind
  • Titel und Nachspann sind separate Animationen, die ebenfalls im Programm berechnet werden und Einzelbilder ausgeben
  • danach Erzeugung einer kompletten Testanimation ohne Ton im Videoschnittbereich um in Synth1 Synthesizereinstellungen vornehmen zu können.

 Ablauf Synth1:

  • Auswahl einer Synthesizervoreinstellung, die zur Animation passt (nicht langweilig, aber auch nicht zu dominant) und kleinere Anpassungen an der Voreinstellung
  • grobes Einspielen des Synthesizers passend zur Animation als MIDI-Datei, dann kleinere Anpassungen an der MIDI-Datei um das Timing zu verbessern und letztendliche Ausgabe als WAV-Datei

Endschnitt in Blender:

  • hinzufügen der Einzelbilder von Titel, Animation und Nachspann zu Videospuren
  • hinzufügen der WAV-Datei zur Audiospur
  • Ausgabe der kompletten Animation als MP4-Datei.

Das ist meine Animation zum Thema “Das Client-Server-Modell”

Storyboard

Im Studiengang Informationsmanagement an der Hochschule Hannover sind im Rahmen des Kurses Entwicklung von Multimediasystemen I (Wintersemester 2017/18, Prof. Dr.-Ing. Steinberg) einige sehr gute Videos rundum das Studium Informationsmanagement und das studentische Leben an der Expo Plaza in Hannover entstanden. Dabei wurden unterschiedliche, klassische Techniken wie z.B. Erklärvideo, Legetechnik oder Stopmotion verwendet.

Die besten Videos stellen wir euch hier nach und nach vor.

Progressive Web Apps für IM: Informationsmanagement-Info

WebLab Hsh: PWA IM-Info

Informationsmanagement-Info als PWA

Autoren: Laili Ghods, Cemre Toppuz und Ogulcan Aktas

PWA: Informationsmanagment – Info

Quellcode

Ziel:

Unsere PWA soll einige allgemeine Informationen über unseren Studiengang vorstellen. Dabei haben wir uns die Texte von der offiziellen Webseite des Studiengangs genommen: https://im.f3.hs-hannover.de/studium/

Anforderungen

Die Progressive Web App soll folgende Anforderungen (Use-Cases) erfüllen:

  1. Navigation:
    • Anforderung: Navigation
    • Beschreibung: Dem Nutzer soll es möglich sein, durch die Applikation zu navigieren. Dabei sollen Links für die einzelnen Ansichten zur Verfügung stehen.
    • Ziel: Übersichtlich verteilter Content
    • Akteure: Systemadministratoren, Informationsmanager
    • Detailbeschreibung: Die Navigation soll alle Ansichten der Webseite erreichbar machen. Vor allem soll dem Nutzer klar sein, welche Funktion die Links der Navigation haben.
    • Mockup:

       2. Progressive Web App

    • Anforderung: Progressive Web App
    • Beschreibung: Die Applikation soll eine Progressive Web App sein und demnach alle üblichen Anforderungen dafür erfüllen.
    • Ziel: Gute User Experience
    • Akteure:  Systemadministratoren, Informationsmanager
    • Detailbeschreibung:
      Quelle: https://developers.google.com/web/progressive-web-apps/checklist

      • Alle App Urls laden auch wenn man offline ist
      • Metadata sind so gesetzt, dass man die App zum Home-Screen hinzufügen kann
      • Jede Seite hat eine gültige URL
    • Mockup:

        3. Single Page Applikation

    • Anforderung: Single Page Applikation
    • Beschreibung: Die Applikation soll als Single Page Applikation umgesetzt werden.
    • Ziel: Bessere User Experience, schnellere Ansicht der einzelnen Seiten
    • Akteure: Systemadministratoren, Informationsmanager
    • Detailbeschreibung: Die Seite soll nur einmal geladen werden vom Browser. Daraufhin soll der komplette Content ohne neuladen der Seite erreichbar sein. Klickt man auf die Navigation, wird im Content Bereich der Seiteninhalt angezeigt, ohne das die komplette Seite neu lädt.
    • Mockup:  n/a

Implementierung

1. React App erstellen

Zuerst galt es die React App zu erstellen. Dafür haben wir ein nützliches Tool benutzt: https://github.com/facebook/create-react-app

create-react-app erlaubt es mit einigen wenigen Kommandozeilen eine React App zu erstellen und zu deployen.

Mit dem Tool konnten wir die App mit folgenden Befehl generieren:

Daraus entsteht dann folgende Dateistruktur und App.js:

2. Navigation einbauen (react-router-dom)

Um die Navigation in React zu gewährleisten, haben wir uns für ein weiteres nützliches Tool entschieden: react-router-dom (https://github.com/ReactTraining/react-router/tree/master/packages/react-router-dom)

Der react-router-dom bietet eine Vielzahl an Möglichkeiten an die Navigation so umzusetzen, dass der Code sauber bleibt und die Applikation eine Single-Page-Applikation bleibt. (Beim Aufruf der Unterseiten wird nicht die komplette Seite neu geladen.) Nach Implementation der Navigation mit Hilfe des Tools ergibt sich folgende index.js:

3. Seiteninhalte/Komponenten erstellen

Wir wollen die Unterseiten Inhalt, Struktur, Zeitplan und Praxisphasen von https://im.f3.hs-hannover.de/studium/ in der PWA darstellen. Dafür haben wir jeweils eine JavaScript Datei erstellt. Daraufhin wurden sie in die index.js importiert und in die Navigation eingebunden (siehe Punkt 2 screenshot). Hier als Beispiel die inhalt.js:

Dies wurde analog für die anderen drei Seiten ebenfalls umgesetzt.

4. Aus der React App eine PWA generieren

Als wir die React Applikation soweit fertig hatten, galt es als nächstes daraus auch eine PWA zu generieren. Dafür nutzen wir das Chrome Tool Lighthous – Audits (https://developers.google.com/web/tools/lighthouse/)

Das Tool kann Webseiten darauf prüfen, ob diese PWA geeignet sind. Die Ergebnisse unseres ersten Tests waren wie folgt:

Als wir online recherchiert haben, sind wir auf folgende Anleitung gestoßen:

https://dzone.com/articles/a-step-by-step-tutorial-for-developing-a-progressi

Die Anleitung beschreibt Schritt für Schritt, wie man aus einer react Applikation eine PWA erstellt.

Damit die Seite auch funktionieren kann, wenn der Nutzer offline ist (welches eine Voraussetzung für PWA’s ist), mussten wir einen ServiceWorker registrieren. Dafür haben wir eine worker.js Datei erstellt:

Und die index.html angepasst, sodass die App prüft, ob der genutzte Browser einen ServiceWorker unterstützt:

Natürlich muss auch der ServiceWorker noch in der index.js registriert werden:

Als nächstes haben wir die Splash-Icons hinzugefügt und Anpassung an der manifest.json durchgeführt. Dadurch kann der Nutzer die Webseite als App herunterladen und offline nutzen. Die gesetzten Icons werden dann als Verknüpfungssymbol genutzt:

Nach den Anpassungen gab uns der Test von Lighthouse Audits folgendes Ergebnis:

5. Deployment

Da unsere PWA nun startklar war, mussten wir diese nur noch deployen. Dafür reichte ein einzelner Befehl in der Kommandokonsole:

Damit wurde der Ordner “build” generiert, welcher auf den Produktionsserver (unser webspace) hochgeladen werden konnte.

Link zur Applikation:

http://ogulcanaktas.wp.hs-hannover.de

Im Studiengang Informationsmanagement an der Hochschule Hannover sind im Rahmen des Kurses Entwicklung von Multimediasystemen I (Wintersemester 2018/19, Prof. Dr.-Ing. Steinberg, Viktor Eisenstadt) einige gute Progressive Web Apps (PWA) mithilfe des JavaScript-Frameworks ReactJS entstanden. Verwendete Techniken sind HTML5, CSS3 und JavaScript.

Die besten PWAs stellen wir euch hier in den nächsten Wochen nach und nach vor.

Progressive Web Apps für IM: Interaktiver Stundenplan

Interaktiver Stundenplan

Autoren: Hozan Hassan, Frederik Pusch, Valentin Griese, Marcel Hemmer und Lukas Fischer

PWA: Interaktiver Stundenplan

Quellcode

Idee/Zielsetzung:

Die Progressive Web App (PWA) “Interaktiver Stundenplan” zum Studiengang Informationsmanagement bietet eineÜbers icht über aller Kurse des BIM unterteilt in die jeweiligen Semester. Die Kurse sollen einfach per Drag and Drop in einen Stundenplan gezogen werden.

Umsetzung

HTML und CSS

Der Stundenplan und die Liste der Fächer sind mit HTML und CSS umgesetzt.

Dabei wird bei der Strukturierung der Semester und Kurse mit einer Liste ausgeführt. Mit dem <details>-Tag können Seiteninhalte, in dem Fall die Tabelle mit den Fächern, versteckt werden.

index.html

<details>
    <summary>1. Semester</summary>
    <ul class="auswahl">
        <li id="0">Informationsstrukturen</li><br/>
        <li id="64">Grundlagen der Erschließung</li><br/>
        <li id="65">Relationale Datenbanken</li><br/>
        <li id="66">Grundlegende WWW-Techniken</li><br/>
        <li id="67">Grundlagen der Statistik</li><br/>
        <li id="1">Nutzerforschung 1 - Grundlagen</li><br/>
        <li id="2">Angewandte Kommunikation 1</li><br/>
        <li id="3">Wissenschaftliche Arbeitstechniken</li><br/>
        <li id="4">Grundlagen der Informatik</li><br/>
        <li id="5">Grundlagen der Mathematik</li><br/>
        <li id="6">Grundlagen der Informatik und Mathematik</li><br/>
    </ul>
</details>

Der Stundenplan an sich wird ebenfalls mit einer simplen Tabelle realisiert. Die Felder, in denen später ein Fach reingezogen werden kann, bleiben einfach leer.

index.html

<table>
    <col style="width:5%">
    <col style="width:19%">
    <col style="width:19%">
    <col style="width:19%">
    <col style="width:19%">
    <tr>
        <th class="zeit" style="text-align:center">Zeit</th>
        <th style="text-align:center">Montag</th>
        <th style="text-align:center">Dienstag</th>
        <th style="text-align:center">Mittwoch</th>
        <th style="text-align:center">Donnerstag</th>
        <th style="text-align:center">Freitag</th>
    </tr>
    <tr>
        <td class="zeit">08:00<br/>-09:30</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>
    </tr>
    <tr>
        <td class="zeit">09:30<br/>-11:00</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>
    </tr>
    <tr>
        <td class="zeit">11:00<br/>-12:30</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>
    </tr>
    <tr>
        <td class="zeit">12:30<br/>-14:00</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>
    </tr>
    <tr>
        <td class="zeit">14:00<br/>-15:30</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>
    </tr>
    <tr>
        <td class="zeit">15:30<br/>-17:00</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>
    </tr>
    <tr>
        <td class="zeit">17:00<br/>-18:30</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>
    </tr>
</table>

Framework

Die PWA nutzt die JavaScript-Bibliothek JQuery UI, die die Drag and Drop Funktion sowie die Touch-Bedienung am Stundenplan umsetzt.

Drag and Drop

Erlaubt das Ziehen der Fächer in den Stundenplan.

Funktion.js:

$(function () {
    $("#div_drag li").draggable(
    {
        appendTo: "body",
        helper: "clone",
    });
    startDrop($("#div_drop table td"));
    function startDrop($elements) {
        $elements.droppable({
                hoverClass: "ui-drop-hover",
                over: function (event, ui) {
                    var $this = $(this);
                },
                drop: function (event, ui) {
                    var $this = $(this);
                    $("<span></span>").text(ui.draggable.text()).appendTo(this);
                    $(".auswahl").find(":contains('" + ui.draggable.text() + "')");
                }
        });
    }
});

Optimierung für Touchscreens

Damit die PWA responsiv wird, z.B. auch auf Handys läuft und die Drag and Drop-Funktion auch auf Touchscreens funktioniert, gibt es die Funktion jquery.ui.touch-punch.min. Diese Funktion stammt von touchpunch.

jquery.ui.touch-punch.min

!function(a){function f(a,b){
	if(!(a.originalEvent.touches.length>1)){
		a.preventDefault();
		var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");
		d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)
	}
}
if(a.support.touch="ontouchend"in document,a.support.touch){
	var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){
		var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){
			e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){
				var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")
			}),c.call(b)},b._mouseDestroy=function(){
				var b=this;b.element.unbind({
					touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")
				}),d.call(b)}}}(jQuery);

Buttons

Die Funktionen der Buttons wurden mit JavaScript umgesetzt.

Fächer

Blendet die Liste mit den Fächern aus.

<script>                                                       
    $('#btn').click(function(){$('#div_drag').toggle();});
</script>

Zeit

Blendet die Zeit-Spalte aus.

<script>
    $('#btn_zeit').click(function() {$('.zeit').toggle();}); 
</script>

Zurücksetzten

Lädt das Fenster einfach neu und setzt es einfach auf den Ausgangszustand zurück

Ergebnis und Funktionen

Die Oberfläche ist simpel gehalten und kann in drei Teile geteilt werden. Auf der linken Seite die Kurse, welche nach Semester sortiert sind, auf der anderen Seite der Stundenplan. Über den Hauptelementen sind drei Buttons angeordnet.

Semester & Kurse

Auf der linken Seite stehen die Semester, die per Klick eine Liste mit den Kursen aufklappen lässt. Die danach gezeigten Kurse können dann per Drag and Drop in den Stundenplan gezogen werden.

Stundenplan

Im Plan sind die wesentlichen Merkmale eines Stundenplanes eingepflegt. Wochentage (Spalten), von Montag bis Freitag, und Zeiten (Reihen), in 90 Minuten Phasen, realisieren den Aufbau. Jede zweite Reihe ist dabei grau eingefärbt.

Buttons

Über dem Stundenplan befinden sich drei Buttons: „Fächer“, „Zeit“ und „Zurücksetzten“.

Um die Ansicht übersichtlicher zu machen, kann man mit den Buttons Fächer und Zeit, Teile des Stundenplanes ausblenden. Der Zeit-Button entfernt oder fügt die Zeitleiste links am Plan hinzu.  Per Klick auf „Fächer“ geschieht dasselbe mit den Listen der Kurse. Der dritte Button „zurücksetzten“ löscht alle Einträge im Stundenplan.

 

Im Studiengang Informationsmanagement an der Hochschule Hannover sind im Rahmen des Kurses Entwicklung von Multimediasystemen I (Wintersemester 2018/19, Prof. Dr.-Ing. Steinberg, Viktor Eisenstadt) einige gute Progressive Web Apps (PWA) mithilfe des JavaScript-Frameworks ReactJS entstanden. Verwendete Techniken sind HTML5, CSS3 und JavaScript.

Die besten PWAs stellen wir euch hier in den nächsten Wochen nach und nach vor.

 

Progressive Web Apps für IM: Teilnehmerverzeichnis ‘Social Contact’

Social Contact

Autoren: Matthias Galda, Catharina Ochsner, Anja Preuße, Sarah Gehrmann

fertige PWA
Quellcode

Idee/Zielsetzung

Zweck der Progressive Web Apps (PWA) soll es sein, die Kontaktdaten der Ansprechpartner aufzulisten, die an der Veranstaltung “InfoInMotion2019” am 11. Oktober teilnehmen.

Überlegungen

Die Startseite enthält eine Lightbox. Darin soll der User Informationen über die Website und ihren Zweck sowie über die Veranstaltung „InfoInMotion2019“ erhalten.

Mock-Up der Startseite
Mock-Up der Startseite

Mithilfe einer Suchfunktion soll es dem User möglich sein, nach interessanten Kontakten zu suchen. Hier sollte der User angeben können zu welcher Gruppe er gehört (Studenten, Studieninteressiert oder Unternehmen) und eine Kontaktgruppe wählen, zu der er Informationen erhalten möchte (Studenten, Dozenten oder Unternehmen).

 

 

Mock-Up der Übersichtsseite
Mock-Up der Übersichtsseite

Des Weiteren wurden drei Seiten geplant und erstellt, auf denen die einzelnen Gruppen vorgestellt werden. Dort sollen einzelne Personen oder Unternehmen aufgeführt werden.

Für den Fall, dass der User mehr über eine Person oder ein Unternehmen erfahren will, kann er diese anklicken und wird auf eine Profilseite der jeweiligen Person/ des Unternehmens weitergeleitet.

Zuletzt soll das Impressum die üblichen Informationen über die Autoren enthalten.
In der Kopfzeile kann der User den Namen der Website finden sowie die direkt darunterliegende Navigation mit dem „Burger“-Menü. Die Fußzeile sollte Links zu dem Impressum und zu dem Studiengang enthalten.
Das genaue Design und den Aufbau der PWA haben wir zusätzlich in Mock-Ups festgehalten.

Umsetzung

Für die Umsetzung der Funktionalitäten haben wir das Framework React in Kombination mit JavaScript genutzt. Die Grundstruktur und die Benutzeroberfläche wurde mit HTML5 und CSS3 erstellt.

Das Design der Seite haben wir schlicht gehalten und uns farblich an den Farben der Fakultät III der Hochschule Hannover orientiert.
In der Praxis haben wir eine vereinfachte Version der oben beschriebenen PWA entworfen. Beim Aufruf der Startseite erscheint eine Lightbox, die den Besucher auf die Offline- sowie App-Nutzung hinweist. Direkt auf der Startseite sind Informationen über die Lehrveranstaltung und den Zweck der PWA zu finden. Über die „Suche“ kann der Benutzer nach Personen und Unternehmen suchen und sich die Ergebnisse in einer Tabelle ausgeben lassen.

Ergebnisse in Tabellenform
Ergebnisse in Tabellenform

Die einzelnen Seiten zu den Personen und Unternehmen haben wir zur besseren Übersicht und aus technischen Gründen weggelassen. Allerdings können diese später noch hinzugefügt werden. Das Impressum wurde wie geplant angelegt. Dort findet der Leser die Informationen zu den Autoren. Die Kopf- und Fußzeile wurden ebenfalls wie geplant umgesetzt.

 

Funktionalität

Die Lightbox wird beim Laden der Startseite abgerufen und mittig angezeigt. Mit einem Klick auf das X kann die Lightbox geschlossen werden.


Um die Lightbox anzuzeigen wird einem DIV-Container ein Zustand zugewiesen, der sich ändert, sobald das X angeklickt wird. Beim Neuladen der Startseite wird der Zustand des DIV-Containers auf den zuvor gesetzten Standard zurückgesetzt. Mithilfe einer if-Funktion wird der Zustand des DIV-Containers abgefragt. Entspricht der Zustand dem vorher gesetzten, wird die Box wieder aufgerufen.

Codeausschnitt der Lightbox
Codeausschnitt der Lightbox

Die Daten über die Personen und Unternehmen wurden über die asynchrone API von IndexedDB eingepflegt. Dadurch können die Daten sowohl online als auch offline abgerufen werden. Diese Daten werden ausschließlich für die Suche benötigt. Dabei hat der User die Möglichkeit über ein Dropdown-Menü auszuwählen, welche Zielgruppe er sucht. Dieses Dropdown-Menü wird mithilfe einer Funktion erstellt, die die jeweilige Kategorie eines Dateneintrags sucht und so zusammenfasst, dass keine doppelten Einträge erscheinen. Durch das Auslesen der Kategorie kann die Tabelle so gefiltert werden, dass nur Einträge der entsprechenden Kategorie aufgelistet werden.

Für die mobile Ansicht haben wir die Tabelle so beschrieben, dass sie auch horizontal scrollbar ist. Ansonsten wäre eine mobile Ansicht nur schwer realisierbar gewesen.

Im Studiengang Informationsmanagement an der Hochschule Hannover sind im Rahmen des Kurses Entwicklung von Multimediasystemen I (Wintersemester 2018/19, Prof. Dr.-Ing. Steinberg, Viktor Eisenstadt) einige gute Progressive Web Apps (PWA) mithilfe des JavaScript-Frameworks ReactJS entstanden. Verwendete Techniken sind HTML5, CSS3 und JavaScript.

Die besten PWAs stellen wir euch hier in den nächsten Wochen nach und nach vor.