Module: Presenters::ImageTag
- Extended by:
- ActiveSupport::Concern
- Included in:
- Crm::ImagePresenter, Www::ImagePresenter
- Defined in:
- app/concerns/presenters/image_tag.rb
Overview
Renders responsive <img> tags for an Image presenter: native lazy
loading, srcset across viewport widths, schema.org output, and automatic
width/height attributes from the image's aspect ratio.
Constant Summary collapse
- BLANK_PNG_PIXEL_IMG_SRC =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='- VIEWPORT_WIDTHS =
Viewport widths.
[3840, 2048, 1920, 1600, 1200, 1080, 915, 750, 500, 320, 160].freeze
Instance Attribute Summary collapse
-
#href ⇒ Object
readonly
Returns the value of attribute href.
-
#imagesizes ⇒ Object
readonly
Returns the value of attribute imagesizes.
-
#imagesrcset ⇒ Object
readonly
Returns the value of attribute imagesrcset.
Instance Method Summary collapse
-
#image_tag(options = {}) ⇒ String
Renders an
<img>tag for the image, stripping request-level keys first. -
#image_tag_html(options) ⇒ String
Lazy loading: Uses browser's native loading="lazy" by default - true (default) - Native browser lazy loading (loads when near viewport) - false - No lazy loading, loads immediately (use for LCP/above-fold images).
-
#to_schema_dot_org ⇒ SchemaDotOrg::ImageObject
Builds a schema.org ImageObject for the underlying image (url, dimensions, MIME type) for structured-data output.
Instance Attribute Details
#href ⇒ Object (readonly)
Returns the value of attribute href.
13 14 15 |
# File 'app/concerns/presenters/image_tag.rb', line 13 def href @href end |
#imagesizes ⇒ Object (readonly)
Returns the value of attribute imagesizes.
13 14 15 |
# File 'app/concerns/presenters/image_tag.rb', line 13 def imagesizes @imagesizes end |
#imagesrcset ⇒ Object (readonly)
Returns the value of attribute imagesrcset.
13 14 15 |
# File 'app/concerns/presenters/image_tag.rb', line 13 def imagesrcset @imagesrcset end |
Instance Method Details
#image_tag(options = {}) ⇒ String
Renders an <img> tag for the image, stripping request-level keys first.
45 46 47 48 49 |
# File 'app/concerns/presenters/image_tag.rb', line 45 def image_tag( = {}) .delete(:request_format) .delete(:amp) image_tag_html() end |
#image_tag_html(options) ⇒ String
Lazy loading: Uses browser's native loading="lazy" by default
- true (default) - Native browser lazy loading (loads when near viewport)
- false - No lazy loading, loads immediately (use for LCP/above-fold images)
Native lazy loading has 95%+ browser support and requires no JavaScript.
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
# File 'app/concerns/presenters/image_tag.rb', line 81 def image_tag_html() = .dup # Extract tag specific param here # img_options[:relative] = false if img_options[:relative].nil? css_class = .delete(:class) style = .delete(:style) itemprop = .delete(:itemprop) fetchpriority = .delete(:fetchpriority) custom_data = .delete(:data) lazyload = .delete(:lazyload) lazyload = true if lazyload.nil? alt = .delete(:alt) || image.try(:seo_title) || image.title title = .delete(:title) || image.title.presence || alt include_srcset = .delete(:include_srcset) # crop_aspect_ratio: height/width float (e.g. 600.0/1920 for a hero banner). # Applies a proportional height to every srcset entry without specifying a fixed # width, so the srcset covers all viewport sizes including Retina (3840w etc.). crop_aspect_ratio = .delete(:crop_aspect_ratio) @imagesizes = sizes = .delete(:sizes) ratio = nil include_srcset = if sizes.present? true elsif include_srcset.nil? true else include_srcset.to_b end = .delete(:viewport_widths) || VIEWPORT_WIDTHS.dup [:encode_format] ||= @options&.dig(:img_options, :encode_format) = .slice(*Image::VALID_IMAGE_URL_OPTIONS) # extracted requested width if any to reduce our srcset requested_width = [:width] requested_width ||= [:size]&.scan(/\A(\d+)x?/)&.flatten&.first requested_width = requested_width.to_i if requested_width requested_height = [:height] requested_height ||= [:size]&.scan(/\A(\d+)x(\d+)?/)&.flatten&.last requested_height = requested_height.to_i if requested_height ratio = requested_height.to_f / requested_width if requested_width && requested_height && requested_width > 0 if requested_width # Include the requested width into the srcset if requested_width < 150 # don't bother with a srcset on an image this small include_srcset = false else << requested_width = .filter_map(&:presence).map(&:to_i).uniq.sort.reverse end end = .compact url = image.image_url(**) srcsets = [] if include_srcset # Based on https://medium.com/hceverything/applying-srcset-choosing-the-right-sizes-for-responsive-images-at-different-breakpoints-a0433450a4a3 = .reject { |w| requested_width && w > requested_width } # The width returned are for a full width viewport. Remember to pair with a sizes attribute to give the # browser some guidance on the final rendering of the image or use the lazy option. srcsets = .map do |width| # Create a new transform set with the viewport width and passing key transformations to preserve = .slice(:relative, :encode_format, :crop_x, :crop_y, :crop_w, :crop_last, :crop_mode, :crop_h, :borderx, :bordery, :rotate, :relative, :hostname, :cache, :webp, :optimize, :thumbnail, :background, :focus) = { width: width, ** } # Apply explicit ratio (from width+height args) or crop_aspect_ratio for a # proportional height crop at every srcset breakpoint (e.g. hero banners). effective_ratio = ratio || crop_aspect_ratio [:height] = (width * effective_ratio).round if effective_ratio = .compact "#{image.image_url(**)} #{width}w" end end @imagesrcset = strset_str = srcsets.join(',').presence params = { alt: alt, title: title, class: css_class, itemprop: itemprop, style: style } css_class_ary = css_class&.split || [] # Set image source and srcset params[:src] = url if strset_str.present? params[:srcset] = strset_str params[:sizes] = sizes if sizes.present? end # Native browser lazy loading (default: true) # Use lazyload: false for above-fold/LCP images params[:loading] = 'lazy' if lazyload css_class_ary = css_class_ary.uniq.compact if css_class_ary.present? params[:class] = css_class_ary.join(' ') else params.delete(:class) end params[:width] ||= [:tag_width] if [:tag_width] params[:height] ||= [:tag_height] if [:tag_height] if params[:width].nil? && params[:height].nil? if requested_width && requested_height # Both dimensions explicitly requested (e.g., size: '150x150') params[:width] = requested_width params[:height] = requested_height elsif requested_width && image.aspect_ratio # Only width requested - calculate height from aspect ratio params[:width] = requested_width params[:height] = (requested_width.to_f / image.aspect_ratio).round elsif ![:no_native_size_in_tag].to_b && && image.aspect_ratio params[:width] = [, 1920].min params[:height] = (params[:width].to_f / image.aspect_ratio).round end end params[:data] = (custom_data || {}).merge(iid: image.id) @href = params[:src] # Add fetchpriority for LCP images (only makes sense without lazy loading) params[:fetchpriority] = fetchpriority if fetchpriority.present? && !lazyload h.tag(:img, **params) end |
#to_schema_dot_org ⇒ SchemaDotOrg::ImageObject
Builds a schema.org ImageObject for the underlying image (url, dimensions,
MIME type) for structured-data output.
24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
# File 'app/concerns/presenters/image_tag.rb', line 24 def to_schema_dot_org url = image.image_url width = image. height = image. content_type = image.determine_mime_type SchemaDotOrg::ImageObject.new( url: url, width: width, height: height, encodingFormat: content_type ) end |