image_rs/yew.rs
1#![doc = include_str!("../YEW.md")]
2
3use crate::common::{
4 AriaLive, AriaPressed, CrossOrigin, Decoding, FetchPriority, Layout, Loading, ObjectFit,
5 Position, ReferrerPolicy,
6};
7use gloo_net::http::Request;
8use wasm_bindgen_futures::spawn_local;
9use web_sys::IntersectionObserverEntry;
10use web_sys::js_sys;
11use web_sys::wasm_bindgen::JsCast;
12use web_sys::wasm_bindgen::JsValue;
13use web_sys::wasm_bindgen::prelude::*;
14use web_sys::{IntersectionObserver, IntersectionObserverInit, RequestCache};
15use yew::prelude::*;
16
17/// Properties for the `Image` component.
18///
19/// The `Image` component allows you to display an image with various customization options
20/// for layout, styling, and behavior. It supports fallback images, lazy loading, and custom
21/// callbacks for error handling and loading completion.
22///
23/// This component is highly flexible, providing support for multiple image layouts,
24/// object-fit, object-position, ARIA attributes, and more.
25///
26/// # See Also
27/// - [MDN img Element](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/img)
28#[derive(Properties, Clone, PartialEq)]
29pub struct ImageProps {
30 /// The source URL of the image.
31 ///
32 /// This is the URL of the image to be displayed. This property is required for loading
33 /// an image. If not provided, the image will not be displayed.
34 #[prop_or_default]
35 pub src: &'static str,
36
37 /// The alternative text for the image.
38 ///
39 /// This is the alt text for the image, which is used for accessibility purposes.
40 /// If not provided, the alt text will be empty.
41 #[prop_or_default]
42 pub alt: &'static str,
43
44 /// Optional fallback image.
45 ///
46 /// This image will be displayed if the main image fails to load. If not provided,
47 /// the image will attempt to load without a fallback.
48 #[prop_or_default]
49 pub fallback_src: &'static str,
50
51 /// The width of the image.
52 ///
53 /// Specifies the width of the image in pixels. It is typically used for responsive
54 /// layouts. Defaults to an empty string if not provided.
55 #[prop_or_default]
56 pub width: &'static str,
57
58 /// The height of the image.
59 ///
60 /// Specifies the height of the image in pixels. Like `width`, it is often used for
61 /// responsive layouts. Defaults to an empty string if not provided.
62 #[prop_or_default]
63 pub height: &'static str,
64
65 // Common props
66 /// The style attribute for the image.
67 ///
68 /// Allows you to apply custom inline CSS styles to the image. Defaults to an empty string.
69 #[prop_or_default]
70 pub style: &'static str,
71
72 /// The CSS class for the image.
73 ///
74 /// This can be used to apply custom CSS classes to the image for styling purposes.
75 /// Defaults to an empty string if not provided.
76 #[prop_or_default]
77 pub class: &'static str,
78
79 /// The sizes attribute for the image.
80 ///
81 /// This is used to define different image sizes for different viewport widths, helping
82 /// with responsive images. Defaults to an empty string if not provided.
83 #[prop_or_default]
84 pub sizes: &'static str,
85
86 /// The quality attribute for the image.
87 ///
88 /// Allows you to set the quality of the image (e.g., "low", "medium", "high"). Defaults
89 /// to an empty string if not provided.
90 #[prop_or_default]
91 pub quality: &'static str,
92
93 /// Indicates if the image should have priority loading.
94 ///
95 /// This controls whether the image should be loaded eagerly (immediately) or lazily
96 /// (when it enters the viewport). Defaults to `false`.
97 #[prop_or_default]
98 pub loading: Loading,
99
100 /// The placeholder attribute for the image.
101 ///
102 /// Allows you to specify a placeholder image URL or data URL to show while the main
103 /// image is loading. Defaults to an empty string.
104 #[prop_or_default]
105 pub placeholder: &'static str,
106
107 /// Callback function for handling loading completion.
108 ///
109 /// This callback is triggered once the image has finished loading. This is useful for
110 /// actions that should happen after the image has been fully loaded, such as hiding
111 /// a loading spinner. Defaults to a no-op.
112 #[prop_or_default]
113 pub on_load: Callback<()>,
114
115 // Advanced Props
116 /// The object-fit attribute for the image.
117 ///
118 /// Determines how the image should be resized to fit its container. Common values include
119 /// "contain", "cover", "fill", etc. Defaults to an empty string.
120 #[prop_or_default]
121 pub object_fit: ObjectFit,
122
123 /// The object-position attribute for the image.
124 ///
125 /// Specifies how the image should be positioned within its container when `object-fit` is set.
126 /// The available options are:
127 /// - `Position::Center`: Centers the image within the container.
128 /// - `Position::Top`: Aligns the image to the top of the container.
129 /// - `Position::Bottom`: Aligns the image to the bottom of the container.
130 /// - `Position::Left`: Aligns the image to the left of the container.
131 /// - `Position::Right`: Aligns the image to the right of the container.
132 /// - `Position::TopLeft`: Aligns the image to the top-left of the container.
133 /// - `Position::TopRight`: Aligns the image to the top-right of the container.
134 /// - `Position::BottomLeft`: Aligns the image to the bottom-left of the container.
135 /// - `Position::BottomRight`: Aligns the image to the bottom-right of the container.
136 ///
137 /// Defaults to `Position::Center`.
138 #[prop_or_default]
139 pub object_position: Position,
140
141 /// Callback function for handling errors during image loading.
142 ///
143 /// This callback is triggered if the image fails to load, allowing you to handle
144 /// error states (e.g., displaying a fallback image or showing an error message).
145 #[prop_or_default]
146 pub on_error: Callback<String>,
147
148 /// The decoding attribute for the image.
149 ///
150 /// Specifies how the image should be decoded. The available options are:
151 /// - `Decoding::Auto`: The image decoding behavior is automatically decided by the browser.
152 /// - `Decoding::Sync`: The image is decoded synchronously (blocking other tasks).
153 /// - `Decoding::Async`: The image is decoded asynchronously (non-blocking).
154 ///
155 /// Defaults to `Decoding::Auto`.
156 #[prop_or_default]
157 pub decoding: Decoding,
158
159 /// The blur data URL for placeholder image.
160 ///
161 /// This is used to display a low-quality blurred version of the image while the full
162 /// image is loading. Defaults to an empty string.
163 #[prop_or_default]
164 pub blur_data_url: &'static str,
165
166 /// The lazy boundary for lazy loading.
167 ///
168 /// Defines the distance (in pixels) from the viewport at which the image should start
169 /// loading. Defaults to an empty string.
170 #[prop_or_default]
171 pub lazy_boundary: &'static str,
172
173 /// Indicates if the image should be unoptimized.
174 ///
175 /// If set to `true`, the image will be loaded without any optimization applied (e.g.,
176 /// no resizing or compression). Defaults to `false`.
177 #[prop_or_default]
178 pub unoptimized: bool,
179
180 /// Image layout.
181 ///
182 /// Specifies how the image should be laid out within its container. Possible values
183 /// include `Layout::Fill`, `Layout::Responsive`, `Layout::Intrinsic`, `Layout::Fixed`,
184 /// `Layout::Auto`, `Layout::Stretch`, and `Layout::ScaleDown`. Defaults to `Layout::Responsive`.
185 #[prop_or_default]
186 pub layout: Layout,
187
188 /// Reference to the DOM node.
189 ///
190 /// This is used to create a reference to the actual DOM element of the image. It is
191 /// useful for directly manipulating the image element via JavaScript if needed.
192 #[prop_or_default]
193 pub node_ref: NodeRef,
194
195 /// A list of one or more image sources for responsive loading.
196 ///
197 /// Defines multiple image resources for the browser to choose from, depending on screen size, resolution,
198 /// and other factors. Each source can include width (`w`) or pixel density (`x`) descriptors.
199 #[prop_or_default]
200 pub srcset: &'static str,
201
202 /// Cross-origin policy to use when fetching the image.
203 ///
204 /// Determines whether the image should be fetched with CORS enabled. Useful when the image needs to be accessed
205 /// in a `<canvas>` element. Accepts `anonymous` or `use-credentials`.
206 #[prop_or_default]
207 pub crossorigin: CrossOrigin,
208
209 /// Referrer policy to apply when fetching the image.
210 ///
211 /// Controls how much referrer information should be included with requests made for the image resource.
212 /// Common values include `no-referrer`, `origin`, `strict-origin-when-cross-origin`, etc.
213 #[prop_or_default]
214 pub referrerpolicy: ReferrerPolicy,
215
216 /// The fragment identifier of the image map to use.
217 ///
218 /// Associates the image with a `<map>` element, enabling clickable regions within the image. The value
219 /// should begin with `#` and match the `name` of the corresponding map element.
220 #[prop_or_default]
221 pub usemap: &'static str,
222
223 /// Indicates that the image is part of a server-side image map.
224 ///
225 /// When set, clicking the image will send the click coordinates to the server. Only allowed when the image
226 /// is inside an `<a>` element with a valid `href`.
227 #[prop_or_default]
228 pub ismap: bool,
229
230 /// Hints the browser about the priority of fetching this image.
231 ///
232 /// Helps the browser prioritize network resource loading. Accepts `high`, `low`, or `auto` (default).
233 /// See `HTMLImageElement.fetchPriority` for more.
234 #[prop_or_default]
235 pub fetchpriority: FetchPriority,
236
237 /// Identifier for tracking image performance timing.
238 ///
239 /// Registers the image with the `PerformanceElementTiming` API using the given string as its ID. Useful for
240 /// performance monitoring and analytics.
241 #[prop_or_default]
242 pub elementtiming: &'static str,
243
244 /// URL(s) to send Attribution Reporting requests for the image.
245 ///
246 /// Indicates that the browser should send an `Attribution-Reporting-Eligible` header with the image request.
247 /// Can be a boolean or a list of URLs for attribution registration on specified servers. Experimental feature.
248 #[prop_or_default]
249 pub attributionsrc: &'static str,
250
251 /// Indicates the current state of the image in a navigation menu.
252 ///
253 /// Valid values are "page", "step", "location", "date", "time", "true", "false".
254 /// This is useful for enhancing accessibility in navigation menus.
255 #[prop_or_default]
256 pub aria_current: &'static str,
257
258 /// Describes the image using the ID of the element that provides a description.
259 ///
260 /// The ID of the element that describes the image. This is used for accessibility
261 /// purposes, particularly for screen readers.
262 #[prop_or_default]
263 pub aria_describedby: &'static str,
264
265 /// Indicates whether the content associated with the image is currently expanded or collapsed.
266 ///
267 /// This is typically used for ARIA-based accessibility and is represented as "true", "false", or "undefined".
268 #[prop_or_default]
269 pub aria_expanded: &'static str,
270
271 /// Indicates whether the image is currently hidden from the user.
272 ///
273 /// This attribute is used for accessibility and indicates whether the image is visible
274 /// to the user or not. Valid values are "true", "false", or "undefined".
275 #[prop_or_default]
276 pub aria_hidden: &'static str,
277
278 /// Indicates whether the content associated with the image is live and dynamic.
279 ///
280 /// The value can be "off", "assertive", or "polite", helping assistive technologies
281 /// determine how to handle updates to the content.
282 #[prop_or_default]
283 pub aria_live: AriaLive,
284
285 /// Indicates whether the image is currently pressed or selected.
286 ///
287 /// This attribute can have values like "true", "false", "mixed", or "undefined".
288 #[prop_or_default]
289 pub aria_pressed: AriaPressed,
290
291 /// ID of the element that the image controls or owns.
292 ///
293 /// Specifies the ID of the element that the image controls or is associated with.
294 #[prop_or_default]
295 pub aria_controls: &'static str,
296
297 /// ID of the element that labels the image.
298 ///
299 /// Specifies the ID of the element that labels the image for accessibility purposes.
300 #[prop_or_default]
301 pub aria_labelledby: &'static str,
302}
303
304impl Default for ImageProps {
305 fn default() -> Self {
306 ImageProps {
307 src: "",
308 alt: "Image",
309 width: "",
310 height: "",
311 style: "",
312 class: "",
313 sizes: "",
314 quality: "",
315 placeholder: "empty",
316 on_load: Callback::noop(),
317 object_fit: ObjectFit::default(),
318 object_position: Position::default(),
319 on_error: Callback::noop(),
320 decoding: Decoding::default(),
321 blur_data_url: "",
322 lazy_boundary: "100px",
323 unoptimized: false,
324 layout: Layout::default(),
325 node_ref: NodeRef::default(),
326 fallback_src: "",
327 srcset: "",
328 crossorigin: CrossOrigin::default(),
329 loading: Loading::default(),
330 referrerpolicy: ReferrerPolicy::default(),
331 usemap: "",
332 ismap: false,
333 fetchpriority: FetchPriority::default(),
334 elementtiming: "",
335 attributionsrc: "",
336 aria_current: "",
337 aria_describedby: "",
338 aria_expanded: "",
339 aria_hidden: "",
340 aria_live: AriaLive::default(),
341 aria_pressed: AriaPressed::default(),
342 aria_controls: "",
343 aria_labelledby: "",
344 }
345 }
346}
347
348/// Image Component
349///
350/// A highly optimized and feature-rich `Image` component for Yew applications, supporting
351/// lazy loading, blur placeholders, fallback handling, and multiple responsive layouts.
352///
353/// # Properties
354/// The component uses the `ImageProps` struct for its properties. Key properties include:
355///
356/// - **src**: The main image source URL (`&'static str`). Required.
357/// - **alt**: Alternative text for accessibility (`&'static str`). Default: `""`.
358/// - **layout**: The image layout strategy (`Layout`). Default: `Layout::Auto`.
359/// - **width**: The width of the image (`&'static str`). Required for certain layouts.
360/// - **height**: The height of the image (`&'static str`). Required for certain layouts.
361/// - **sizes**: Defines responsive image sizes (`&'static str`). Default: `""`.
362/// - **quality**: Image quality (custom property) (`&'static str`). Optional.
363/// - **placeholder**: Placeholder strategy before the image loads (e.g., `"blur"`) (`&'static str`). Default: `""`.
364/// - **blur_data_url**: Base64-encoded low-res placeholder image (`&'static str`). Used when `placeholder` is `"blur"`.
365/// - **fallback_src**: Fallback image URL if the main `src` fails to load (`&'static str`). Optional.
366/// - **priority**: Whether to load the image eagerly instead of lazily (`bool`). Default: `false`.
367/// - **object_fit**: CSS `object-fit` value (`ObjectFit`). Default: `ObjectFit::Contain`.
368/// - **object_position**: Object positioning inside the container (`Position`). Default: `Position::Center`.
369/// - **style**: Additional inline CSS styles (`&'static str`). Default: `""`.
370/// - **class**: Additional CSS classes (`&'static str`). Default: `""`.
371/// - **decoding**: Decoding strategy (`Decoding`). Default: `Decoding::Auto`.
372/// - **on_load**: Callback invoked when the image successfully loads (`Callback<()>`). Default: no-op.
373/// - **on_error**: Callback invoked if loading or fallback loading fails (`Callback<String>`). Default: no-op.
374/// - **node_ref**: Node reference for the underlying `img` element (`NodeRef`).
375/// - **ARIA attributes**: Full ARIA support for better accessibility (`aria_label`, `aria_hidden`, etc.).
376///
377/// # Features
378/// - **Lazy Loading with IntersectionObserver**:
379/// Image is loaded **only when scrolled into view**, boosting performance and saving bandwidth.
380///
381/// - **Fallback Handling**:
382/// Automatically tries a `fallback_src` if the primary `src` fails, ensuring robustness.
383///
384/// - **Blur Placeholder**:
385/// Supports blurred low-resolution placeholders for smoother image transitions.
386///
387/// - **Multiple Layouts**:
388/// Supports various layouts like `Fill`, `Responsive`, `Intrinsic`, `Fixed`, `Auto`, `Stretch`, and `ScaleDown`.
389///
390/// - **Accessibility**:
391/// Built-in support for ARIA attributes to make images fully accessible.
392///
393/// # Layout Modes
394/// - `Layout::Fill`: Image stretches to fill its container absolutely.
395/// - `Layout::Responsive`: Maintains aspect ratio and scales responsively.
396/// - `Layout::Intrinsic`: Renders using natural image dimensions.
397/// - `Layout::Fixed`: Renders at a fixed size.
398/// - `Layout::Auto`: Default natural behavior without forcing constraints.
399/// - `Layout::Stretch`: Fills the parent container's width and height.
400/// - `Layout::ScaleDown`: Scales image down to fit the container without stretching.
401///
402/// # Examples
403///
404/// ## Basic Usage
405/// ```rust
406/// use yew::prelude::*;
407/// use image_rs::yew::Image;
408/// use image_rs::Layout;
409///
410/// #[function_component(App)]
411/// pub fn app() -> Html {
412/// html! {
413/// <Image
414/// src="/images/photo.jpg"
415/// alt="A beautiful view"
416/// width="800"
417/// height="600"
418/// layout={Layout::Responsive}
419/// />
420/// }
421/// }
422/// ```
423///
424/// ## Blur Placeholder
425/// ```rust
426/// use yew::prelude::*;
427/// use image_rs::yew::Image;
428/// use image_rs::Layout;
429///
430/// #[function_component(App)]
431/// pub fn app() -> Html {
432/// html! {
433/// <Image
434/// src="/images/photo.jpg"
435/// alt="Blur example"
436/// width="800"
437/// height="600"
438/// layout={Layout::Intrinsic}
439/// placeholder="blur"
440/// blur_data_url="data:image/jpeg;base64,..."
441/// />
442/// }
443/// }
444/// ```
445///
446/// ## Fallback Image
447/// ```rust
448/// use yew::prelude::*;
449/// use image_rs::yew::Image;
450/// use image_rs::Layout;
451///
452/// #[function_component(App)]
453/// pub fn app() -> Html {
454/// html! {
455/// <Image
456/// src="/images/main.jpg"
457/// fallback_src="/images/fallback.jpg"
458/// alt="With fallback"
459/// width="800"
460/// height="600"
461/// layout={Layout::Fixed}
462/// />
463/// }
464/// }
465/// ```
466///
467/// # Behavior
468/// - The image starts loading lazily once it enters the viewport (10% visible threshold).
469/// - If loading fails, a network fetch checks the fallback image.
470/// - Blur placeholder is rendered with a heavy `blur(20px)` effect until the full image loads.
471/// - Depending on the `Layout`, the container styling adjusts automatically.
472///
473/// # Notes
474/// - `width` and `height` are required for `Responsive`, `Intrinsic`, and `Fixed` layouts.
475/// - `Layout::Fill` ignores width and height, stretching to fit the parent container.
476/// - Accessibility attributes like `aria-label` and `aria-hidden` are passed directly to the `<img>` element.
477/// - Priority images (`priority = true`) are loaded eagerly instead of lazily.
478///
479/// # Errors
480/// - If both `src` and `fallback_src` fail, the `on_error` callback is triggered with an error message.
481/// - JSON parsing from a fallback response is attempted but not mandatory for image loading success.
482///
483/// # Optimization Techniques
484/// - **IntersectionObserver** is used for intelligent lazy loading.
485/// - **Caching** via `RequestCache::Reload` ensures fallback images are always fetched fresh if needed.
486/// - **Async/await** approach for fetch operations provides non-blocking fallback handling.
487///
488/// # See Also
489/// - [MDN img Element](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/img)
490#[function_component]
491pub fn Image(props: &ImageProps) -> Html {
492 let mut props = props.clone();
493 let img_ref = props.node_ref.clone();
494
495 let img_ref_clone = img_ref.clone();
496 let on_load = props.on_load.clone();
497 let on_load_call = props.on_load.clone();
498
499 // Lazy Load Effect:
500 // Waits until the image **scrolls into view**, then dynamically **sets the src** to start loading it.
501 // Triggers an optional `on_load` callback once loading is initiated.
502 // Smart Optimization: Saves bandwidth and greatly improves page speed, especially for pages with **many images**!
503 // 9000 IQ Move: Only load images users actually *scroll to*, no more wasting bytes, gg!
504 use_effect_with(JsValue::from(props.src), move |_deps| {
505 let callback = Closure::wrap(Box::new(
506 move |entries: js_sys::Array, _observer: IntersectionObserver| {
507 if let Some(entry) = entries.get(0).dyn_ref::<IntersectionObserverEntry>() {
508 if entry.is_intersecting() {
509 if let Some(img) = img_ref_clone.cast::<web_sys::HtmlImageElement>() {
510 img.set_src(props.src);
511 on_load.emit(());
512 }
513 }
514 }
515 },
516 )
517 as Box<dyn FnMut(js_sys::Array, IntersectionObserver)>);
518
519 let options = IntersectionObserverInit::new();
520 // e.g., 10% visible
521 options.set_threshold(&js_sys::Array::of1(&0.1.into()));
522 // if Root is None, defaults to viewport
523
524 // Create observer
525 let observer =
526 IntersectionObserver::new_with_options(callback.as_ref().unchecked_ref(), &options)
527 .expect("Failed to create IntersectionObserver");
528
529 // Start observing
530 if let Some(img) = img_ref.cast::<web_sys::HtmlElement>() {
531 observer.observe(&img);
532 }
533
534 // Disconnect when unmount
535 let observer_clone = observer.clone();
536 let _cleanup = move || {
537 observer_clone.disconnect();
538 };
539
540 // Keep closure alive
541 callback.forget();
542 });
543
544 // This informs your app that the image failed to load and auto replace the image.
545 let fetch_data = {
546 Callback::from(move |_| {
547 let loading_complete_callback = props.on_load.clone();
548 let on_error_callback = props.on_error.clone();
549 spawn_local(async move {
550 match Request::get(props.fallback_src)
551 .cache(RequestCache::Reload)
552 .send()
553 .await
554 {
555 Ok(response) => {
556 if response.status() == 200 {
557 let json_result = response.json::<serde_json::Value>();
558 match json_result.await {
559 Ok(_data) => {
560 props.src = props.fallback_src;
561 loading_complete_callback.emit(());
562 }
563 Err(_err) => {
564 on_error_callback.emit("Image Not Found!".to_string());
565 }
566 }
567 } else {
568 let status = response.status();
569 let body = response.text().await.unwrap_or_else(|_| {
570 String::from("Failed to retrieve response body")
571 });
572 on_error_callback.emit(format!(
573 "Failed to load image. Status: {}, Body: {:?}",
574 status, body
575 ));
576 }
577 }
578
579 Err(err) => {
580 // Handle network errors
581 on_error_callback.emit(format!("Network error: {}", err));
582 }
583 }
584 });
585 })
586 };
587
588 let img_style = {
589 let mut style = String::new();
590 style.push_str(&format!("object-fit: {};", props.object_fit.as_str()));
591 style.push_str(&format!(
592 "object-position: {};",
593 props.object_position.as_str()
594 ));
595 if !props.style.is_empty() {
596 style.push_str(props.style);
597 }
598 style
599 };
600
601 let blur_style = if props.placeholder == "blur" {
602 format!(
603 "background-size: {}; background-position: {}; filter: blur(20px); background-image: url(\"{}\")",
604 props.sizes,
605 props.object_position.as_str(),
606 props.blur_data_url
607 )
608 } else {
609 String::new()
610 };
611
612 let onload = {
613 Callback::from(move |_| {
614 on_load_call.emit(());
615 })
616 };
617
618 let layout = match props.layout {
619 Layout::Fill => {
620 html! {
621 <span style={"display: block; position: absolute; top: 0; left: 0; bottom: 0; right: 0;"}>
622 <img
623 src={props.src}
624 alt={props.alt}
625 width={props.width}
626 height={props.height}
627 style={img_style}
628 class={props.class}
629 loading={props.loading.as_str()}
630 sizes={props.sizes}
631 quality={props.quality}
632 placeholder={props.placeholder}
633 decoding={props.decoding.as_str()}
634 ref={props.node_ref}
635 role="img"
636 aria-label={props.alt}
637 aria-labelledby={props.aria_labelledby}
638 aria-describedby={props.aria_describedby}
639 aria-hidden={props.aria_hidden}
640 aria-current={props.aria_current}
641 aria-expanded={props.aria_expanded}
642 aria-live={props.aria_live.as_str()}
643 aria-pressed={props.aria_pressed.as_str()}
644 aria-controls={props.aria_controls}
645 onerror={fetch_data}
646 style={blur_style}
647 crossorigin={props.crossorigin.as_str()}
648 referrerpolicy={props.referrerpolicy.as_str()}
649 fetchpriority={props.fetchpriority.as_str()}
650 attributionsrc={props.attributionsrc}
651 onload={onload}
652 elementtiming={props.elementtiming}
653 srcset={props.srcset}
654 ismap={props.ismap}
655 usemap={props.usemap}
656 />
657 </span>
658 }
659 }
660 Layout::Responsive => {
661 let quotient: f64 =
662 props.height.parse::<f64>().unwrap() / props.width.parse::<f64>().unwrap();
663 let padding_top: String = if quotient.is_nan() {
664 "100%".to_string()
665 } else {
666 format!("{}%", quotient * 100.0)
667 };
668
669 html! {
670 <span style={"display: block; position: relative;"}>
671 <span style={"padding-top: ".to_owned() + &padding_top}>
672 <img
673 src={props.src}
674 alt={props.alt}
675 width={props.width}
676 height={props.height}
677 style={img_style}
678 class={props.class}
679 sizes={props.sizes}
680 quality={props.quality}
681 loading={props.loading.as_str()}
682 placeholder={props.placeholder}
683 decoding={props.decoding.as_str()}
684 ref={props.node_ref}
685 role="img"
686 aria-label={props.alt}
687 aria-labelledby={props.aria_labelledby}
688 aria-describedby={props.aria_describedby}
689 aria-hidden={props.aria_hidden}
690 aria-current={props.aria_current}
691 aria-expanded={props.aria_expanded}
692 aria-live={props.aria_live.as_str()}
693 aria-pressed={props.aria_pressed.as_str()}
694 aria-controls={props.aria_controls}
695 onerror={fetch_data}
696 style={blur_style}
697 crossorigin={props.crossorigin.as_str()}
698 referrerpolicy={props.referrerpolicy.as_str()}
699 fetchpriority={props.fetchpriority.as_str()}
700 attributionsrc={props.attributionsrc}
701 onload={onload}
702 elementtiming={props.elementtiming}
703 srcset={props.srcset}
704 ismap={props.ismap}
705 usemap={props.usemap}
706 />
707 </span>
708 </span>
709 }
710 }
711 Layout::Intrinsic => {
712 html! {
713 <span style={"display: inline-block; position: relative; max-width: 100%;"}>
714 <span style={"max-width: 100%;"}>
715 <img
716 src={props.src}
717 alt={props.alt}
718 width={props.width}
719 height={props.height}
720 style={img_style}
721 class={props.class}
722 sizes={props.sizes}
723 loading={props.loading.as_str()}
724 quality={props.quality}
725 placeholder={props.placeholder}
726 decoding={props.decoding.as_str()}
727 ref={props.node_ref}
728 role="img"
729 aria-label={props.alt}
730 aria-labelledby={props.aria_labelledby}
731 aria-describedby={props.aria_describedby}
732 aria-hidden={props.aria_hidden}
733 aria-current={props.aria_current}
734 aria-expanded={props.aria_expanded}
735 aria-live={props.aria_live.as_str()}
736 aria-pressed={props.aria_pressed.as_str()}
737 aria-controls={props.aria_controls}
738 onerror={fetch_data}
739 style={blur_style}
740 crossorigin={props.crossorigin.as_str()}
741 referrerpolicy={props.referrerpolicy.as_str()}
742 fetchpriority={props.fetchpriority.as_str()}
743 attributionsrc={props.attributionsrc}
744 onload={onload}
745 elementtiming={props.elementtiming}
746 srcset={props.srcset}
747 ismap={props.ismap}
748 usemap={props.usemap}
749 />
750 </span>
751 <img
752 src={props.blur_data_url}
753 style={"display: none;"}
754 alt={props.alt}
755 aria-hidden="true"
756 />
757 </span>
758 }
759 }
760 Layout::Fixed => {
761 html! {
762 <span style={"display: inline-block; position: relative;"}>
763 <img
764 src={props.src}
765 alt={props.alt}
766 width={props.width}
767 height={props.height}
768 style={img_style}
769 class={props.class}
770 sizes={props.sizes}
771 quality={props.quality}
772 loading={props.loading.as_str()}
773 placeholder={props.placeholder}
774 decoding={props.decoding.as_str()}
775 ref={props.node_ref}
776 role="img"
777 aria-label={props.alt}
778 aria-labelledby={props.aria_labelledby}
779 aria-describedby={props.aria_describedby}
780 aria-hidden={props.aria_hidden}
781 aria-current={props.aria_current}
782 aria-expanded={props.aria_expanded}
783 aria-live={props.aria_live.as_str()}
784 aria-pressed={props.aria_pressed.as_str()}
785 aria-controls={props.aria_controls}
786 onerror={fetch_data}
787 style={blur_style}
788 crossorigin={props.crossorigin.as_str()}
789 referrerpolicy={props.referrerpolicy.as_str()}
790 fetchpriority={props.fetchpriority.as_str()}
791 attributionsrc={props.attributionsrc}
792 onload={onload}
793 elementtiming={props.elementtiming}
794 srcset={props.srcset}
795 ismap={props.ismap}
796 usemap={props.usemap}
797 />
798 </span>
799 }
800 }
801 Layout::Auto => {
802 // Preserve the natural size of the image
803 html! {
804 <span style={"display: inline-block; position: relative;"}>
805 <img
806 src={props.src}
807 alt={props.alt}
808 width={props.width}
809 height={props.height}
810 style={img_style}
811 class={props.class}
812 sizes={props.sizes}
813 quality={props.quality}
814 placeholder={props.placeholder}
815 loading={props.loading.as_str()}
816 decoding={props.decoding.as_str()}
817 ref={props.node_ref}
818 role="img"
819 aria-label={props.alt}
820 aria-labelledby={props.aria_labelledby}
821 aria-describedby={props.aria_describedby}
822 aria-hidden={props.aria_hidden}
823 aria-current={props.aria_current}
824 aria-expanded={props.aria_expanded}
825 aria-live={props.aria_live.as_str()}
826 aria-pressed={props.aria_pressed.as_str()}
827 aria-controls={props.aria_controls}
828 onerror={fetch_data}
829 style={blur_style}
830 crossorigin={props.crossorigin.as_str()}
831 referrerpolicy={props.referrerpolicy.as_str()}
832 fetchpriority={props.fetchpriority.as_str()}
833 attributionsrc={props.attributionsrc}
834 onload={onload}
835 elementtiming={props.elementtiming}
836 srcset={props.srcset}
837 ismap={props.ismap}
838 usemap={props.usemap}
839 />
840 </span>
841 }
842 }
843 Layout::Stretch => {
844 // Make the image fill the container
845 html! {
846 <span style={"display: block; width: 100%; height: 100%; position: relative;"}>
847 <img
848 src={props.src}
849 alt={props.alt}
850 width="100%"
851 height="100%"
852 style={img_style}
853 class={props.class}
854 loading={props.loading.as_str()}
855 sizes={props.sizes}
856 quality={props.quality}
857 placeholder={props.placeholder}
858 decoding={props.decoding.as_str()}
859 ref={props.node_ref}
860 role="img"
861 aria-label={props.alt}
862 aria-labelledby={props.aria_labelledby}
863 aria-describedby={props.aria_describedby}
864 aria-hidden={props.aria_hidden}
865 aria-current={props.aria_current}
866 aria-expanded={props.aria_expanded}
867 aria-live={props.aria_live.as_str()}
868 aria-pressed={props.aria_pressed.as_str()}
869 aria-controls={props.aria_controls}
870 onerror={fetch_data}
871 style={blur_style}
872 crossorigin={props.crossorigin.as_str()}
873 referrerpolicy={props.referrerpolicy.as_str()}
874 fetchpriority={props.fetchpriority.as_str()}
875 attributionsrc={props.attributionsrc}
876 onload={onload}
877 elementtiming={props.elementtiming}
878 srcset={props.srcset}
879 ismap={props.ismap}
880 usemap={props.usemap}
881 />
882 </span>
883 }
884 }
885 Layout::ScaleDown => {
886 // Maintain aspect ratio, but scale down if image is too large
887 html! {
888 <span style={"display: inline-block; position: relative; max-width: 100%; max-height: 100%;"}>
889 <img
890 src={props.src}
891 alt={props.alt}
892 width={props.width}
893 height={props.height}
894 style={img_style}
895 class={props.class}
896 loading={props.loading.as_str()}
897 sizes={props.sizes}
898 quality={props.quality}
899 placeholder={props.placeholder}
900 decoding={props.decoding.as_str()}
901 ref={props.node_ref}
902 role="img"
903 aria-label={props.alt}
904 aria-labelledby={props.aria_labelledby}
905 aria-describedby={props.aria_describedby}
906 aria-hidden={props.aria_hidden}
907 aria-current={props.aria_current}
908 aria-expanded={props.aria_expanded}
909 aria-live={props.aria_live.as_str()}
910 aria-pressed={props.aria_pressed.as_str()}
911 aria-controls={props.aria_controls}
912 onerror={fetch_data}
913 style={blur_style}
914 crossorigin={props.crossorigin.as_str()}
915 referrerpolicy={props.referrerpolicy.as_str()}
916 fetchpriority={props.fetchpriority.as_str()}
917 attributionsrc={props.attributionsrc}
918 onload={onload}
919 elementtiming={props.elementtiming}
920 srcset={props.srcset}
921 ismap={props.ismap}
922 usemap={props.usemap}
923 />
924 </span>
925 }
926 }
927 };
928 html! {
929 {layout}
930 }
931}