class Inkcite::Renderer::SpecialEffect

Constants

OPACITY_CEIL
OPACITY_MAX
OPACITY_MIN

Opacity constraints on the children

PI_OVER_180

For converting degrees to radians and back

POSITION_CEIL
POSITION_FLOOR

Position min and max preventing animated elements from leaving the bounds of the container.

SIZE_MAX
SIZE_MIN

Size constraints on the animated children.

SPEED_MAX
SPEED_MIN

Speed constraints on the children.

Public Instance Methods

render(tag, opt, ctx) click to toggle source
# File lib/inkcite/renderer/special_effect.rb, line 229
def render tag, opt, ctx

  # If the closing tag was received (e.g. /snow) then close the wrapper
  # div that was rendered by the opening tag.
  return '</div>' if tag.start_with?('/')

  # Retrieve the special effects default values (times, number of units, etc.)
  _defaults = defaults(opt, ctx)

  # Create a special effects context that simplifies working with the
  # opts, defaults and manages the styles/classes necessary to animate
  # the special effect.
  sfx = EffectContext.new(tag, opt, ctx, _defaults)

  # Provide the extending class with an opportunity to configure the
  # effect context prior to any rendering.
  config_effect_context sfx

  html = []
  styles = []

  # If this is the first special effect to be included in the email
  # we need to disable the CSS animation from Gmail - which only
  # accepts part of its <styles> leading to unexpected whitespace.
  # By putting this invalid CSS into the <style> block, Gmail's
  # very strict parser will exclude the entire block, preventing
  # the animation from running.
  # https://emails.hteumeuleu.com/troubleshooting-gmails-responsive-design-support-ad124178bf81#.8jh1vn9mw
  if ctx.email? && sfx.uuid == 1
    styles << Inkcite::Renderer::Style.new(".gmail-fix", sfx.ctx, { FONT_SIZE => '3*px' })
  end

  # True if we're limiting the rendering of the animation to iOS clients
  only_ios = ctx.email? && !ctx.development?

  styles << '@media screen and (-webkit-min-device-pixel-ratio:0) {' if only_ios

  # Create the <div> that wraps the entire animation.
  create_wrap_element html, sfx

  # Create the Style that defines the look of the wrapping container
  create_wrap_style styles, sfx

  # Create the Style that is applied to all children in the animation.
  create_all_children_style styles, sfx

  # Now create each of the child elements (e.g. the snowflakes) that
  # will be animated in this effect.  Each child is created and animated
  # at the same time.
  create_child_elements html, styles, sfx

  # Append all of the Keyframes to the end of the styles, now that
  # the individual children are configured.
  sfx.animations.each { |a| styles << a.to_keyframe_css }

  styles << '}' if only_ios

  # Push the completed list of styles into the context's stack.
  ctx.styles << styles.join("\n")

  html.join("\n")

end

Protected Instance Methods

config_all_children(style, sfx) click to toggle source

The extending class can override this method to perform any additional configuration on the style that affects all children in the animation.

# File lib/inkcite/renderer/special_effect.rb, line 319
def config_all_children style, sfx
  # This space left intentionally blank
end
config_child(n, child, style, animation, sfx) click to toggle source

The extending class must implement this method to customize and animate each child.

# File lib/inkcite/renderer/special_effect.rb, line 325
def config_child n, child, style, animation, sfx
  raise 'Classes extending SpecialEffect must implement defaults(child, style, animation, keyframes, sfx)'
end
config_effect_context(sfx) click to toggle source

The extending class can implement this method to customize the EffectContext prior to any HTML or CSS generation.

# File lib/inkcite/renderer/special_effect.rb, line 331
def config_effect_context sfx
  # This space left intentionally blank
end
config_wrap_element(div, sfx) click to toggle source

The extending class can override this method to perform any additional configuration on the <div> that wraps the entire animation.

# File lib/inkcite/renderer/special_effect.rb, line 338
def config_wrap_element div, sfx
  # This space left intentionally blank
end
config_wrap_style(style, sfx) click to toggle source

The extending class can override this method to customize the wrap <div>'s style.

# File lib/inkcite/renderer/special_effect.rb, line 344
def config_wrap_style style, sfx
  # This space left intentionally blank
end
defaults(opt, ctx) click to toggle source

The extending class must override this method and return the defaults for the special effect as a map.

# File lib/inkcite/renderer/special_effect.rb, line 350
def defaults opt, ctx
  raise 'Classes extending SpecialEffect must implement defaults(opt, ctx)'
end

Private Instance Methods

create_all_children_style(styles, sfx) click to toggle source

Creates the Style that applies to /all/ children.

# File lib/inkcite/renderer/special_effect.rb, line 357
def create_all_children_style styles, sfx

  style = Inkcite::Renderer::Style.new(".#{sfx.all_children_class_name}", sfx.ctx, { :position => :absolute })

  # If no image has been provided, make the background a solid color
  # otherwise set the background to the image source and fill the
  # available space.
  src = sfx.src
  if src.blank?
    color = sfx.color
    style[BACKGROUND_COLOR] = color unless none?(color)
  else
    style[BACKGROUND_IMAGE] = "url(#{sfx.ctx.image_url(src.first)})" unless src.length > 1
    style[BACKGROUND_SIZE] = '100%'
  end

  # Provide the extending class with a chance to apply additional
  # styles to all children.
  config_all_children style, sfx

  styles << style

end
create_child_elements(html, styles, sfx) click to toggle source

Creates n-number of child <div> objects and assigns the all-child and each-child CSS classes to them allowing each to be sized, animated uniquely.

# File lib/inkcite/renderer/special_effect.rb, line 383
def create_child_elements html, styles, sfx

  # Get a local handle on the src array in case we need to apply
  # a random one to each child (because there are multiple)
  src = sfx.src

  sfx.count.times do |n|

    child_class_name = sfx.child_class_name(n)

    # This is the child HTML element
    child = Inkcite::Renderer::Element.new('div', { :class => quote("#{sfx.all_children_class_name} #{child_class_name}") })

    # This is the custom style to be applied to the child.
    style = Inkcite::Renderer::Style.new(".#{child_class_name}", sfx.ctx)

    # If there are multiple images, apply the next image based on the
    # child's index.
    if src.length > 1
      src_index = n % src.length
      style[BACKGROUND_IMAGE] = "url(#{sfx.ctx.image_url(src[src_index])})"
    end

    # This is the animation declaration (timing, duration, etc.) for this child.
    animation = Inkcite::Animation.new(sfx.animation_class_name(n), sfx.ctx)

    # Provide the extending class with a chance to configure the child
    # and its style.
    config_child n, child, style, animation, sfx

    # Add the child's HTML element into the email.
    html << child.to_s + '</div>'

    # If the extending class actually defined an animation for this child
    # then assign it and add it to the list of animations to be appended
    # after the styles are injected.
    unless animation.blank?

      sfx.animations << animation

      # If the extending class didn't assign the animation already, then
      # assign it to the child's style - this adds itself with the appropriate
      # browser prefixes.
      style[:animation] = animation if style[:animation].blank?

    end

    # Append the child's custom style, unless blank (meaning the extending
    # class did not customize the child's styles directly).
    styles << style unless style.blank?

  end

end
create_wrap_element(html, sfx) click to toggle source

Creates the <div> that wraps the entire animation. The children of this container will be animated based on the parameters of the effect.

# File lib/inkcite/renderer/special_effect.rb, line 440
def create_wrap_element html, sfx

  # Initialize the wrap that will hold each of the children and wraps the content
  # over which the special effect will animate.
  div = Inkcite::Renderer::Element.new('div', { :class => quote(sfx.wrap_class_name) })

  # Background color gets applied directly to the div so it renders consistently
  # in all clients - even those that don't support special effects.
  mix_background div, sfx.opt, sfx.ctx

  # Text alignment within the wrapper
  mix_text_align div, sfx.opt, sfx.ctx

  # Provide the extending class with a chance to make additional
  # configuration changes to the wrap element.
  config_wrap_element div, sfx

  html << div.to_s

end
create_wrap_style(styles, sfx) click to toggle source
# File lib/inkcite/renderer/special_effect.rb, line 461
def create_wrap_style styles, sfx

  # Initialize the class declaration that will be applied to the
  # wrapping container.
  style = Inkcite::Renderer::Style.new(".#{sfx.wrap_class_name}", sfx.ctx, { :position => :relative, :overflow => :hidden, :width => '100%' })

  # If a specific height has been specified for the wrap class, add
  # it to the style.
  height = sfx.height
  style[:height] = px(height) if height > 0

  # Provide the extending class with a chance to do any additional
  # customization to this style.
  config_wrap_style style, sfx

  styles << style

end