All about coding

Ruby and Rails technical content written by Lucian Ghinda

How to make a small pulsating animation

#ruby #ruby-on-rails #tailwind-css #animation

How to create a new animation for Tailwind and use it in your Rails app

The other day, I added a deals page to the GoodEnoughTesting website and considered making the menu item a bit more visible. I wanted it to pulsate just a bit, without being too much.

Tailwind already has an animation called pulse, but I wanted something else. I wanted something that would scale a bit and then come back.

This is what I implemented:

First, I added the following in the application.tailwind.css where I define a 3 keyframes, and I scale it just a bit over at 50% (or the middle frame):

@keyframes pulsate {
  0% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.10);
  }
  100% {
    transform: scale(1);
  }
}

Then I extended Tailwind to include this as animation, by adding the following in the config/tailwind.config.js:

module.exports = {
  theme: {
    extend: {
+      animation: {
+        'pulsate': 'pulsate 2s ease-in-out infinite',
+      }
    },
  },
}

And now I can use it in the header menu:

<a href="" class="motion-safe:animate-pulsate"> 
  A pulsating link 
</a>

I used motion-safe modifier to respected the choice of the user if they requested reduced motion.

Extracting a component

I am using Phlex when building the UI for the GoodEnoughTesting so I want to extract the menu item to a series of components that I can re-use when needed.

First I created a Menu::ItemComponent:

module Menu
  class ItemComponent < ApplicationComponent
    include Phlex::Rails::Helpers::LinkTo

    CLASS = <<~DEFAULT
      inline-block
      no-underline
      font-bold text-base uppercase leading-6 antialiased
      x-2.5 py-2 px-2
      align-baseline
      hover:bg-blue-200 hover:rounded-lg
    DEFAULT

    def initialize(url:, text: nil, pulsate: false, text_color: "", **options)
      @text = text
      @url = url
      @options = options
      @pulsate = pulsate
      @text_color = text_color
    end

    def template(&)
      if @text.present?
        link_to(@text, @url, class: css_rules)
      else
        link_to(@url, class: css_rules, &)
      end
    end

    private

      def css_rules   = "#{CLASS} #{pulsate_css} #{@text_color}"
      def pulsate_css = (@pulsate ? "motion-safe:animate-pulsate" : "")
  end
end

And then I created simple versions of this component.

Menu::PrimaryComponent is the normal menu item, with a dark gray color:

module Menu
  class PrimaryComponent < ItemComponent
    def initialize(url:, text: nil, **options)
      pulsate = false
      text_color = "text-gray-800"

      super(url:, text:, pulsate:, text_color:, **options)
    end
  end
end

Menu::BlueComponent is a menu item with the blue text:

module Menu
  class BlueComponent < ItemComponent
    def initialize(url:, text: nil, **options)
      pulsate = false
      text_color = "text-blue-700"

      super(url:, text:, pulsate:, text_color:, **options)
    end
  end
end

Menu::PulsatingComponent is the one that is setting the pulsate attribute:

module Menu
  class PulsatingComponent < ItemComponent
    def initialize(url:, text: nil, **options)
      pulsate = true
      text_color = "text-blue-700"

      super(url:, text:, pulsate:, text_color:, **options)
    end
  end
end

And then at the end I combine them inside an ERB file like this:

<%= render Menu::PrimaryComponent.new(url: page_path("articles"), text: "Articles and News") %>
<%= render Menu::PulsatingComponent.new(url: page_path("deals"), text: "Deals", pulsate: true) %>
<%= render Menu::PrimaryComponent.new(url: page_path("about"), text: "About") %>
<%= render Menu::PrimaryComponent.new(url: page_path("contact"), text: "Contact") %>
<%= render Menu::BlueComponent.new(url: participants_sign_in_path, text: "Login") %>

If you like this article:

πŸ‘ Join my live workshop about goodenoughtesting.com - to learn test design techniques for writing effective tests

πŸ‘‰ Join my Short Ruby Newsletter for weekly Ruby updates from the community and visit rubyandrails.info, a directory with learning content about Ruby.

🀝 Let’s connect on Bluesky, Ruby.social, Linkedin, Twitter where I post mostly about Ruby and Ruby on Rails.

πŸŽ₯ Follow me on my YouTube channel for short videos about Ruby/Rails

View posts by tag

#ruby #concurrency #general-advice #life-hack #ruby-on-rails #google-cloud #kubernetes #bash #apis #rest-api #guide #docker #bugs-and-errors #howtos #newbie #programming-languages #programming-tips #til #coding #learning #postgresql #sql #design-patterns #web-api #programming-blogs #programmer #databases #programming #learn-coding #learning-journey #side-project #coding-challenge #design-principles #solid-principles #git #tips #testing #test-driven-development #test-automation #code #shortpost #refactoring #code-review #rails #developer #documentation #focus #blogging #content-creation #share #advice #newsletter #active-record #ai #ai-tools #generative-ai #web-development #errors #cursor-ide #ides #gpt-4 #news #scalability #tailwind-css #chatgpt #opensource #reviews #startups #github #technology #types #sorbet #performance #rubymine #vscode #codesnippet #features #syntax #friendlyrb #conference #directories #writing #summary #animation #articles #podcasts #publishing #patterns #pattern-matching #content #updates #seo-for-developers #marketing #properties #gems #matching #rspec #llm #tools #lsp #claudeai #opencode #mcp #rubocop #quality #planning #products #mvp #upgrade #agentic-ai #skills #superpowers