This is one solution to the Succulent Garden Project Outline I built for the Creative Coding Club. There's always more than one way to code things - I've written this step-by-step walkthrough specifically for beginner to intermediate front-end devs.

I also didn't follow the original mock-ups exactly, and made some minor modifications/improvements (added a button, changed some copy here and there, etc) while building the project. I encourage you to do the same!

JUMP AHEAD


Step 1: Write your markup (HTML)

The project has two primary sections: the "plant drawer" that slides out from the left, and the original "terrarium container" that starts full-screen and then shrinks.

  <main>
  <!-- Plant Drawer -->
  <section class="plant-drawer"></section>

  <!-- Terrarium Container -->
  <section class="terrarium-container"></section>
</main>


Plant Drawer

Inside of the plant drawer are 8 different succulents to choose from. Let's build those into the section, using an img tag for each one. If we wanted to interact with the individual SVGs and animate them, we would include them as inline SVG.

If you didn't get the assets yet, download them here.

Remember to add alt text for accessibility, and let's give each img two class attributes: succulent and original. These will be important later.

    <!-- Plant Drawer -->
  <section class="plant-drawer">
    <div>
      <img class="succulent original" src="path/to/succulent-agave.svg" alt="agave succulent">
    </div>
    <div>
      <img class="succulent original" src="path/to/succulent-barrel-cactus.svg" alt="barrel cactus">
    </div>
    <div>
      <img class="succulent original" src="path/to/succulent-purple-agave.svg" alt="purple agave succulent">
    </div>
    <div>
      <img class="succulent original" src="path/to/succulent-cacti.svg" alt="three cacti grouped together">
    </div>
    <div>
      <img class="succulent original" src="path/to/succulent-purslane.svg" alt="purslane succulent">
    </div>
    <div>
      <img class="succulent original" src="path/to/succulent-snakeplant.svg" alt="snake plant succulent with two flowers">
    </div>
    <div>
      <img class="succulent original" src="path/to/succulent-gnome.svg" alt="a little gnome">
    </div>
    <div>
      <img class="succulent original" src="path/to/succulent-gemstone.svg" alt="decorative purple crystal">
    </div>
  </section>


And let's not forget to tell the user what to do. Let's add that as a <p> element just before the closing </section> tag. We'll position it with CSS later.

      </div>
    <!-- Prompt the user -->
    <p>drag the plants into your terrarium</p>
  </section>


Terrarium Container

Inside of the terrarium container, there are 5 terrarium shapes to choose from. Let's build those into the <section>, using not one, but two <img> elements for each one.

Why two image elements for each terrarium?
Good question! This project is tricky because we need to think about z-index layers. The user has to drag a succulent into the terrarium, meaning it has to live under the terrarium frame, but on top of the soil.

Layers of succulent garden


Next, build 5 div containers and give them the classes terr-wrap and start (again, these classes will be really helpful later). Inside of each div, build two image elements for the soil and the frame. Give the soil image a class of soil and the frame image a class of frame.

    <!-- Terrarium Container -->
  <section class="terrarium-container">
    <div class="terr-wrap start">
      <img class="soil" src="path/to/terrarium-bottle-soil.svg" alt="Bottle-shaped terrarium soil" />
      <img class="frame" src="path/to/terrarium-bottle-frame.svg" alt="Bottle-shaped terrarium outline" />
    </div>
    <div class="terr-wrap start">
      <img class="soil" src="path/to/terrarium-house-soil.svg" alt="House-shaped terrarium soil" />
      <img class="frame" src="path/to/terrarium-house-frame.svg" alt="House-shaped terrarium outline" />
    </div>
    <div class="terr-wrap start">
      <img class="soil" src="path/to/terrarium-dome-soil.svg" alt="Dome-shaped terrarium soil" />
      <img class="frame" src="path/to/terrarium-dome-frame.svg" alt="Dome-shaped terrarium outline" />
    </div>
    <div class="terr-wrap start">
      <img class="soil" src="path/to/terrarium-bowl-soil.svg" alt="Bowl-shaped terrarium soil" />
      <img class="frame" src="path/to/terrarium-bowl-frame.svg" alt="Bowl-shaped terrarium outline" />
    </div>
    <div class="terr-wrap start">
      <img class="soil" src="path/to/terrarium-geo-soil.svg" alt="Geometric terrarium soil" />
      <img class="frame" src="path/to/terrarium-geo-frame.svg" alt="Geometric terrarium outline" />
    </div>
  </section>


Okay great - now we have the terrariums themselves inside the container, but there are a few more things we need:

The "choose your terrarium" prompt.

      <!-- Prompt the user -->
    <p>choose your terrarium</p>


The context menu (to re-order and increase succulent size). I used FontAwesome icons for this. I also added an option to delete succulents, and a div for a thumbnail icon of the selected succulent.

      <!-- Context menu with options to increase/decrease succulent size and re-order -->
    <div class="context-menu">
      <a id="forwards"><i class="fa fa-arrow-up"></i></a>
      <a id="backwards"><i class="fa fa-arrow-down"></i></a>
      <a id="grow"><i class="fa fa-plus"></i></a>
      <a id="shrink"><i class="fa fa-minus"></i></a>
      <a id="trash"><i class="fa fa-trash"></i></a>
      <!-- thumbnail of succulent so user knows which one is selected -->
      <div class="thumb"></div>
    </div>


And some buttons to a) clear the terrarium and b) start a new one.

      <!-- Buttons to clear and start new terrarium -->
    <div class="button-wrap">
      <a class="button clear">clear</a>
      <a class="button new">new terrarium</a>
    </div>


Step 2: Style it up with CSS

Now that our content and project structure is in place, let's start adding some CSS to the layout. Be sure to add a "normalize" or "CSS reset" before you start, so you have an easier time fighting with browser defaults.

Let's tackle the major elements of the layout first: the body, main, and two section elements:

  body {
  background: #333; 
  font-size: 24px;
  letter-spacing: 0.5px;
  font-family: 'EB Garamond', serif;
  line-height: 1.4;
  padding: 10px;
}

main {
  display: flex;
  width: 920px; /* not responsive - just use exact pixel width */
  margin: 0 auto; /* center it */
}

.plant-drawer {
  width: 40%;
  background: #DCEDC8; /* light green background */
  margin-right: 10px;
}

.terrarium-container {
  width: 60%;
  background: #fff;
}


Okay, we're getting there...

Progress shot of CSS so far


Plant Drawer

Let's hide the terrarium container right now so we can focus on the CSS for the plant drawer first. Temporarily add display: none; to .terrarium-container:

  .terrarium-container {
  width: 60%;
  background: #fff;
  display: none; /* hide temporarily */
}


To get the succulents to sit neatly side-by-side, let's add flexbox to the "plant drawer":

  .plant-drawer {
  width: 40%;
  background: #DCEDC8;
  margin-right: 10px;
  display: flex; /* add flexbox */
  flex-wrap: wrap; /* ensure the items inside wrap */
  padding: 20px 0; /* add padding */
}

.plant-drawer div {
  text-align: center; /* center the div content */
  width: 50%; /* make them 50% width */
}


Each succulent div has a white circle as the background. Let's build the circles as background images, and also reduce the opacity of the succulents themselves. On hover, we'll bump the opacity back to 100% and change the cursor to grab.

  .plant-drawer div {
  text-align: center;
  width: 50%;
  background: url(path/to/circle.svg) no-repeat;
  background-position: bottom;
  min-height: 120px;
  position: relative; /* because imgs inside will be absolute */
}

.succulent {
  position: absolute; 
  bottom: 3px;
  max-height: 80%;
  max-width: 90%;
  opacity: 0.7;
  left: 50%;
  transform: translateX(-50%); /* centering hack */
}

.succulent:hover {
  cursor: grab;
  opacity: 1;
}

Progress shot of CSS so far


All divs and succulents are the same size now. Let's go in and adjust the div heights according to the succulent sizes - some are taller, whereas others are smaller. This is pretty nit-picky pixel pushing, so I'll give you the values I came up with.

    <section class="plant-drawer">
    <!-- the first two succulents are smaller -->
    <div class="small">...</div>
    <div class="small">...</div>
    <div>...</div>
    <div>...</div>
    <!-- whereas these two succulents are taller -->
    <div class="tall">...</div>
    <div class="tall">...</div>
    <div>...</div>
    <div>...</div>
    <p>drag the plants into your terrarium</p>
  </section>

  .plant-drawer div.tall {
  height: 230px;
}

.plant-drawer div.small .succulent {
  width: 50%;
}


And before we move on from the plant drawer, let's position that paragraph element that prompts the user to drag the succulents.

  .plant-drawer {
  padding: 20px 0;
  width: 40%;
  background: #DCEDC8;
  margin-right: 10px;
  display: flex;
  flex-wrap: wrap;
  position: relative; /* relative position on the parent */
}

.plant-drawer p {
  position: absolute;
  left: 105%;
  width: 200px;
}


Alrighty, the plant drawer looks pretty good. It needs to be hidden initially anyway, so let's give it a left position of -100%. We also need to reduce the width to 0% so the terrarium container can actually grow to full-width.

And if we're being really nit-picky, we'll move the margin-right property from the plant-drawer class to the open class.

  .plant-drawer {
  padding: 20px 0;
  width: 0%; /* change this to 0% */
  background: #DCEDC8;
  margin-right: 0px; /* change to 0px */
  display: flex;
  flex-wrap: wrap;
  position: relative;
  left: -100%; /* starts "off screen" to the left */
  transition: 2s all; /* a transition when it slides in */
}


Eventually, we're going to use jQuery to add a class of "open" to this section so that it can slide in from the left. Let's code that class into our CSS now too.

  .plant-drawer.open {
  left: 0;
  width: 40%;
  margin-right: 10px;
}


Terrarium Container

Phew! Okay, now that the CSS is written for the plant drawer, let's style the terrarium container. First - comment out (or delete) that temporary line of code we wrote before:

  .terrarium-container {
  width: 60%;
  background: #fff;
  /* display: none; */
}


The terrarium container starts off full-width, and shrinks to 60% width once a user clicks on their terrarium. Revise the width to 100%, and give the container position: relative; and min-height: 100vh;, because we'll be absolutely positioning the terrariums next.

  .terrarium-container {
  width: 100%; /* revise width */
  background: #fff;
  position: relative; /* positioning */
  min-height: 100vh;
}


We're eventually going to use jQuery to add a class of "shrink" to the container, so let's just build that class now before we forget:

  .terrarium-container.shrink {
  width: 60%;
}


Okay - the five terrariums need to be smaller, the frames need to sit perfectly on top of the soil:

  .terr-wrap {
  position: absolute; 
  width: 30%; /* smaller terrariums please */
  transition: 1s all; /* for a nice hover transition later */
}
.terr-wrap .soil,
.terr-wrap .frame {
  width: 100%; /* but the images inside need to be full size */
  opacity: 0.8; /* with a subtle transparency */
}
.frame {
  position: absolute; /* so it sits on top of the soil */
  top: 0;
}


And use some nth-child pseudo selectors to position them:

  .terr-wrap:first-child {
  left: 50%;
  transform: translateX(-50%);
}

.terr-wrap:nth-child(2),
.terr-wrap:nth-child(3) {
  top: 25%;
}

.terr-wrap:nth-child(3) {
  right: 0%;
}

.terr-wrap:nth-child(4),
.terr-wrap:nth-child(5) {
  bottom: 0%;
  left: 15%;
}

.terr-wrap:nth-child(5) {
  left: initial;
  right: 15%;
}


Give them a hover state:

  .terr-wrap:hover {
  width: 33%;
  cursor: pointer;
}

.terr-wrap:hover img {
  opacity: 1;
}


Absolutely center the text:

  .terrarium-container p {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translateX(-50%);
}


Phew, okay... are you still with me? You should have something that looks like this:

More CSS progress - terrariums in place


Just a few more CSS things and then we're moving on to programming interactions. Let's style the context menu to sit in the top right corner.

  .context-menu {
  position: absolute;
  top: 10px; 
  right: 10px;
}
.thumb {
  width: 80px;
  height: 80px;
  position: absolute;
  right: 0;
  opacity:0.4;
}
.fa {
  font-size: 28px;
  margin: 0 5px;
  color: #ccc;
  position: relative;
}
.fa:after {
  font-size: 12px;
  position: absolute;
  bottom: -26px;
  left: 0px;
  opacity: 0;
}
.fa:hover {
  color: rgba(0,0,0,1);
  cursor: pointer;
}


And it's a good idea to give some helper text on hover of each icon, to give the user an idea of what they do. Use more pseudo elements for this.

  .fa:hover:after {
  opacity: 1;
}
.fa-arrow-up:after {
  content: 'bring forward';
}
.fa-arrow-down:after {
  content: 'send back';
}
.fa-minus:after {
  content: 'shrink';
}
.fa-plus:after {
  content: 'grow';
}   
.fa-trash:after {
  content: 'delete';
}


We're almost there. Now style the buttons...

  .button-wrap {
  position:absolute;
  bottom: 20px;
  left: 10px;
}
.button {
  text-decoration: none;
  padding: 12px 22px;
  border: 1px solid #333;
  left: 10px;
  font-size: 13px;
  color: #333;
}
.button:hover {
  cursor: pointer;
  background: #333;
  color: #FFF;
}


And you should have something like this:

CSS progress GIF showing hover states


Hide the context menu and the buttons, and then we're ready to move on to some JS!

  .context-menu {
  position: absolute;
  top: 10px; 
  right: 10px;
  display: none; /* hide on the opening screen */
}

.button-wrap {
  position:absolute;
  bottom: 20px;
  left: 10px;
  display:none; /* hide on the opening screen */
}


Phew! That was a lot of CSS.

Ashley from the bachelor or something, looking overwhelmed

And I wish I could say we're totally done with it, but there's still a tiny bit more coming up to finish off the project. For now though we get to take a break and play with jQuery.


Step 3: Build the jQuery interactions

If you haven't already - make sure to include both jQuery and jQuery UI in this project. jQuery UI has the draggable/droppable methods already built in, which is super handy for our purposes.

Let's kick it off with a click function targeting any one of the 5 terrariums:

  $('.terr-wrap.start').on('click', function(){

});


Cool, so there are a couple things we want to do next:

  • remove the class of "start" from the clicked terrarium
  • add a class of "selected" to the clicked terrarium
  • fade out the other terrariums (the ones that still have "start")
  • fade out the text
  • add a class of shrink to the terrarium container
  • and open the plant drawer by adding a class of "open" to it
  $('.terr-wrap.start').on('click', function(){
  $(this).removeClass('start').addClass('selected');
  $('.terr-wrap.start').fadeOut();
  $('.terrarium-container p').fadeOut();
  $('.terrarium-container').addClass('shrink');
  $('.plant-drawer').addClass('open');
});


And this what it should look like so far: Click interaction progress screen capture GIFs


Well what do you know, not bad. All that CSS paid off!

BUT - the terrarium is not sized yet for the "shrink" container. So, let's write a little bit of CSS to fix that:

  .shrink .terr-wrap {
  width: 100%;
  bottom: initial;
  top: 50%;
  left: 50%;
  transform: translateX(-50%) translateY(-50%);
}
.shrink .terr-wrap .soil,
.shrink .terr-wrap .frame {
  opacity: 1; /* boost up opacity on selected terrarium */
}


A few more things

  • the buttons and context menu should show up
  • the plant drawer prompt should have a higher z-index than the terrarium container.
  .plant-drawer p {
  position: absolute;
  left: 105%;
  width: 200px;
  z-index: 10; /* should be on top of terrarium container */
}

.shrink .button-wrap,
.shrink .context-menu {
  display: block;
}

Screencap of project progress so far


Build the drag-and-drop interaction

The first step for the drag-and-drop interaction is to make the "original" succulents draggable. I call them "original" because the intention for our project is to duplicate or copy each succulent and drag it to the terrarium. Once they are in the terrarium, they are not "original" and therefore cannot be duplicated.

    $('.original').draggable();

Screencap of progress


Okay cool, so the succulents are draggable. But they are not duplicating and they aren't moving into the terrarium <div> in the DOM yet.

The draggable widget has options and a built in helper that will work for that. Let's append the succulents to the selected terrarium, and clone them.

    $('.original').draggable({
    appendTo: ".terr-wrap.selected",
    helper: 'clone'
  });

Screencap of progress


Not quite there yet. The succulents are cloning and dragging now, but they aren't dropping into the terrarium.

The draggable widget is only half of the solution here. Let's look into the droppable widget. Try using the drop event and initialize the droppable with the drop callback.

Then, append the clone of the object(succulent) you have clicked. Build the droppable widget inside of the click function on the terrarium container.

So far, your JS should look like this:

  $('.terr-wrap.start').on('click', function(){
  $(this).removeClass('start').addClass('selected');
  $('.terr-wrap.start').fadeOut();
  $('.terrarium-container p').fadeOut();
  $('.terrarium-container').addClass('shrink');
  $('.plant-drawer').addClass('open');

  // make the selected terrarium container droppable
  $(this).droppable({
    drop: function(e, ui) {
      $(this).append($(ui.helper).clone());
    }
  });
});

$('.original').draggable({
  appendTo: ".terr-wrap.selected",
  helper: 'clone'
});

Screencap of progress - succulents get stuck

Okay, well - we're making progress. The succulents are successfully cloning into the terrarium in the DOM, but we can't drag them around once they are inside.

Also - we need to properly layer the succulents, frame and soil. Right now, the succulents are on top of the soil and the frame, but the frame needs to be on top. Let's get that sorted...


A short css interlude...
    .frame {
    position: absolute;
    top: 0;
    z-index: 300; /* top */
  }
  .terr-wrap .succulent {
    opacity: 1;
    z-index: 20; /* middle */
  }


While we're in CSS land, let's shrink the overall size of the succulents once they're placed in the terrarium. That gnome is gigantic!

I went in and added a class of smaller to the images I thought needed to be smaller than the rest (gnome, crystal, cacti).

    .terr-wrap .succulent {
    opacity: 1;
    z-index: 20;
    transform: scale(0.8);
  }
  .terr-wrap .succulent.smaller {
    transform: scale(0.6);
  }


Now, I'm sure some of you realize this already. We are going to run into a problem. Because the "frame" SVG is now on top of the succulent SVGs, how are we going to drag them around once they are under the frame?

We need to disable pointer events on the frame for this to work. CSS to the rescue:

    .frame {
    position: absolute;
    top: 0;
    z-index: 30; 
    pointer-events: none; /* click through me */
  }


Back to the fun stuff...

The issue now is we can't drag the succulents once they're placed in the terrarium container, so let's make them draggable:

    $(this).droppable({
    drop: function(e, ui) {
      $(this).append($(ui.helper).clone());
      $(this).find('.succulent').draggable();
    }
  });

Screencap of succulents draggable but now duplicating


On one hand, the succulents are now draggable within the terrarium so that's good. But we definitely don't want them duplicating or we'll end up with a gnome army.

We need to remove that class of 'original' from the succulent once it's in the terrarium so they don't duplicate. I think it's time for a conditional statement.

    $(this).droppable({
    drop: function(e, ui) {
      if ($(ui.helper).clone().hasClass('original')) {
        $(this).append($(ui.helper).clone());
        $(this).find('.succulent').removeClass('original');
        $(this).find('.succulent').draggable();
      }
    }
  });


Holy guacamole it's starting to look super great!
Avocados dancing
Now, let's give users the options to shrink/grow succulents, re-order them and erase them entirely.


Build the context-menu controls

I hope you love click functions, because we're coding more of them! We need to build a function that makes a planted succulent "editable" once it's clicked. To indicate to the user which succulent they have selected in "edit" mode, we'll change the thumbnail image of that succulent in the top right corner.

  // context menu
$('.terr-wrap').on('click', '.succulent', function(){
  $('.terr-wrap .succulent').removeClass('edit');
  $(this).addClass('edit');
  var bg = $(this).attr('src');
  $('.thumb').css({
    "background": "url(" + bg + ") no-repeat",
    "background-size": "contain",
    "background-position": "50% 50%",
  });
});


Next, we need to build in click functionality for each of the control icons (shrink, grow, send back, send forward and delete). We'll use the jQuery CSS method for the most part, and .remove() to delete a succulent.


Shrink + grow succulents

Our succulent SVGs are currently sized using the CSS transform property, and they have different scale values depending on if they are large or smaller sized plants.

It's a bit tricky to get the correct transform: scale() value of each succulent using jQuery, unless we introduce some kind of regex. Instead - we're going to leverage the CSS matrix to help us.

Morpheus from The Matrix

Here we go: create a function to get the correct scale value.

  //calculate correct scale value
function getScale() {
  // get the transform matrix of the clicked succulent
  var div = $('.edit').css('transform');

  // turn the matrix string into an array of values
  var values = div.split('(')[1];
  values = values.split(')')[0];
  values = values.split(',');

  // return the transform scale values we need from the matrix array
  var a = values[0];
  var b = values[1];

  // this is how to get the correct scale value out of the css matrix
  return Math.sqrt(a*a + b*b);
}


For those of you right now like "ummm is that a square root?" - yes. Yes it is. Understanding the math behind the CSS matrix is outside of the scope of this tutorial, but for you mega nerds - take the red pill.


Finish context menu and buttons

Final stretch! Next - let's build some click functions for the context menu icons, using our new getScale() function to grow/shrink succulents by 5%.

  $('.fa-plus').on('click',function(){
  $('.edit').css('transform', 'scale(' + ( getScale() + 0.05 ) + ')') ;
});

$('.fa-minus').on('click',function(){
  $('.edit').css('transform', 'scale(' + ( getScale() - 0.05 ) + ')') ;
});


That was the hard part - the next few click functions are straight-forward by comparison. Next up, let's build the sorting functionality. To send a succulent forwards, we will increase the z-index value by 1, and vice versa.

  $('.fa-arrow-up').on('click',function(){
  var level = $('.edit').css('z-index');
  $('.edit').css('z-index', level + 1);
});

$('.fa-arrow-down').on('click',function(){
  var level = $('.edit').css('z-index');
  $('.edit').css('z-index', level - 1);
});


And, let's hook up the trash icon to remove the selected succulent.

  $('.fa-trash').on('click',function(){
  $('.edit').remove();
});

Screencap of succulents dropping, sorting and scaling


AMAZING! We're almost there! Let's write a few more lines of code to hook up the "clear" and "start over" buttons.

Rocky running a race

  // Buttons
$('.clear').on('click',function(){
  $('.terr-wrap.selected .succulent').remove();
});

$('.new').on('click', function(){
  $('.terr-wrap.selected .succulent').remove();
  $('.terr-wrap.selected').removeClass('selected').addClass('start');
  $('.terr-wrap.start').fadeIn();
  $('.terrarium-container p').fadeIn();
  $('.terrarium-container').removeClass('shrink');
  $('.plant-drawer').removeClass('open');
});

Screencap of the button functionality


You did it!

There are a couple more improvements or modifications you could make, of course. I strongly encourage anyone reading this to be creative and go beyond the scope of this tutorial.

  • Can you make it responsive?
  • Can you fix the cursor so it's always centered on the SVG regardless of size?
  • Can you build in an option for users to save their final succulent garden as an image? Maybe using html2canvas ?

How would you improve it or remix it? Fork my code and make it better! Write a blog post on your process, too - so we call all learn!

Thanks everyone. Looking forward to the next project!


2,964 1 48