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 full_style = format!("{} {}", blur_style, img_style);
619
620 let layout = match props.layout {
621 Layout::Fill => {
622 html! {
623 <span style={"display: block; position: absolute; top: 0; left: 0; bottom: 0; right: 0;"}>
624 <img
625 src={props.src}
626 alt={props.alt}
627 width={props.width}
628 height={props.height}
629 style={full_style}
630 class={props.class}
631 loading={props.loading.as_str()}
632 sizes={props.sizes}
633 quality={props.quality}
634 placeholder={props.placeholder}
635 decoding={props.decoding.as_str()}
636 ref={props.node_ref}
637 role="img"
638 aria-label={props.alt}
639 aria-labelledby={props.aria_labelledby}
640 aria-describedby={props.aria_describedby}
641 aria-hidden={props.aria_hidden}
642 aria-current={props.aria_current}
643 aria-expanded={props.aria_expanded}
644 aria-live={props.aria_live.as_str()}
645 aria-pressed={props.aria_pressed.as_str()}
646 aria-controls={props.aria_controls}
647 onerror={fetch_data}
648 crossorigin={props.crossorigin.as_str()}
649 referrerpolicy={props.referrerpolicy.as_str()}
650 fetchpriority={props.fetchpriority.as_str()}
651 attributionsrc={props.attributionsrc}
652 onload={onload}
653 elementtiming={props.elementtiming}
654 srcset={props.srcset}
655 ismap={props.ismap}
656 usemap={props.usemap}
657 />
658 </span>
659 }
660 }
661 Layout::Responsive => {
662 let quotient: f64 =
663 props.height.parse::<f64>().unwrap() / props.width.parse::<f64>().unwrap();
664 let padding_top: String = if quotient.is_nan() {
665 "100%".to_string()
666 } else {
667 format!("{}%", quotient * 100.0)
668 };
669
670 html! {
671 <span style={"display: block; position: relative;"}>
672 <span style={"padding-top: ".to_owned() + &padding_top}>
673 <img
674 src={props.src}
675 alt={props.alt}
676 width={props.width}
677 height={props.height}
678 style={full_style}
679 class={props.class}
680 sizes={props.sizes}
681 quality={props.quality}
682 loading={props.loading.as_str()}
683 placeholder={props.placeholder}
684 decoding={props.decoding.as_str()}
685 ref={props.node_ref}
686 role="img"
687 aria-label={props.alt}
688 aria-labelledby={props.aria_labelledby}
689 aria-describedby={props.aria_describedby}
690 aria-hidden={props.aria_hidden}
691 aria-current={props.aria_current}
692 aria-expanded={props.aria_expanded}
693 aria-live={props.aria_live.as_str()}
694 aria-pressed={props.aria_pressed.as_str()}
695 aria-controls={props.aria_controls}
696 onerror={fetch_data}
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={full_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 crossorigin={props.crossorigin.as_str()}
740 referrerpolicy={props.referrerpolicy.as_str()}
741 fetchpriority={props.fetchpriority.as_str()}
742 attributionsrc={props.attributionsrc}
743 onload={onload}
744 elementtiming={props.elementtiming}
745 srcset={props.srcset}
746 ismap={props.ismap}
747 usemap={props.usemap}
748 />
749 </span>
750 <img
751 src={props.blur_data_url}
752 style={"display: none;"}
753 alt={props.alt}
754 aria-hidden="true"
755 />
756 </span>
757 }
758 }
759 Layout::Fixed => {
760 html! {
761 <span style={"display: inline-block; position: relative;"}>
762 <img
763 src={props.src}
764 alt={props.alt}
765 width={props.width}
766 height={props.height}
767 style={full_style}
768 class={props.class}
769 sizes={props.sizes}
770 quality={props.quality}
771 loading={props.loading.as_str()}
772 placeholder={props.placeholder}
773 decoding={props.decoding.as_str()}
774 ref={props.node_ref}
775 role="img"
776 aria-label={props.alt}
777 aria-labelledby={props.aria_labelledby}
778 aria-describedby={props.aria_describedby}
779 aria-hidden={props.aria_hidden}
780 aria-current={props.aria_current}
781 aria-expanded={props.aria_expanded}
782 aria-live={props.aria_live.as_str()}
783 aria-pressed={props.aria_pressed.as_str()}
784 aria-controls={props.aria_controls}
785 onerror={fetch_data}
786 crossorigin={props.crossorigin.as_str()}
787 referrerpolicy={props.referrerpolicy.as_str()}
788 fetchpriority={props.fetchpriority.as_str()}
789 attributionsrc={props.attributionsrc}
790 onload={onload}
791 elementtiming={props.elementtiming}
792 srcset={props.srcset}
793 ismap={props.ismap}
794 usemap={props.usemap}
795 />
796 </span>
797 }
798 }
799 Layout::Auto => {
800 // Preserve the natural size of the image
801 html! {
802 <span style={"display: inline-block; position: relative;"}>
803 <img
804 src={props.src}
805 alt={props.alt}
806 width={props.width}
807 height={props.height}
808 style={full_style}
809 class={props.class}
810 sizes={props.sizes}
811 quality={props.quality}
812 placeholder={props.placeholder}
813 loading={props.loading.as_str()}
814 decoding={props.decoding.as_str()}
815 ref={props.node_ref}
816 role="img"
817 aria-label={props.alt}
818 aria-labelledby={props.aria_labelledby}
819 aria-describedby={props.aria_describedby}
820 aria-hidden={props.aria_hidden}
821 aria-current={props.aria_current}
822 aria-expanded={props.aria_expanded}
823 aria-live={props.aria_live.as_str()}
824 aria-pressed={props.aria_pressed.as_str()}
825 aria-controls={props.aria_controls}
826 onerror={fetch_data}
827 crossorigin={props.crossorigin.as_str()}
828 referrerpolicy={props.referrerpolicy.as_str()}
829 fetchpriority={props.fetchpriority.as_str()}
830 attributionsrc={props.attributionsrc}
831 onload={onload}
832 elementtiming={props.elementtiming}
833 srcset={props.srcset}
834 ismap={props.ismap}
835 usemap={props.usemap}
836 />
837 </span>
838 }
839 }
840 Layout::Stretch => {
841 // Make the image fill the container
842 html! {
843 <span style={"display: block; width: 100%; height: 100%; position: relative;"}>
844 <img
845 src={props.src}
846 alt={props.alt}
847 width="100%"
848 height="100%"
849 style={full_style}
850 class={props.class}
851 loading={props.loading.as_str()}
852 sizes={props.sizes}
853 quality={props.quality}
854 placeholder={props.placeholder}
855 decoding={props.decoding.as_str()}
856 ref={props.node_ref}
857 role="img"
858 aria-label={props.alt}
859 aria-labelledby={props.aria_labelledby}
860 aria-describedby={props.aria_describedby}
861 aria-hidden={props.aria_hidden}
862 aria-current={props.aria_current}
863 aria-expanded={props.aria_expanded}
864 aria-live={props.aria_live.as_str()}
865 aria-pressed={props.aria_pressed.as_str()}
866 aria-controls={props.aria_controls}
867 onerror={fetch_data}
868 crossorigin={props.crossorigin.as_str()}
869 referrerpolicy={props.referrerpolicy.as_str()}
870 fetchpriority={props.fetchpriority.as_str()}
871 attributionsrc={props.attributionsrc}
872 onload={onload}
873 elementtiming={props.elementtiming}
874 srcset={props.srcset}
875 ismap={props.ismap}
876 usemap={props.usemap}
877 />
878 </span>
879 }
880 }
881 Layout::ScaleDown => {
882 // Maintain aspect ratio, but scale down if image is too large
883 html! {
884 <span style={"display: inline-block; position: relative; max-width: 100%; max-height: 100%;"}>
885 <img
886 src={props.src}
887 alt={props.alt}
888 width={props.width}
889 height={props.height}
890 style={full_style}
891 class={props.class}
892 loading={props.loading.as_str()}
893 sizes={props.sizes}
894 quality={props.quality}
895 placeholder={props.placeholder}
896 decoding={props.decoding.as_str()}
897 ref={props.node_ref}
898 role="img"
899 aria-label={props.alt}
900 aria-labelledby={props.aria_labelledby}
901 aria-describedby={props.aria_describedby}
902 aria-hidden={props.aria_hidden}
903 aria-current={props.aria_current}
904 aria-expanded={props.aria_expanded}
905 aria-live={props.aria_live.as_str()}
906 aria-pressed={props.aria_pressed.as_str()}
907 aria-controls={props.aria_controls}
908 onerror={fetch_data}
909 crossorigin={props.crossorigin.as_str()}
910 referrerpolicy={props.referrerpolicy.as_str()}
911 fetchpriority={props.fetchpriority.as_str()}
912 attributionsrc={props.attributionsrc}
913 onload={onload}
914 elementtiming={props.elementtiming}
915 srcset={props.srcset}
916 ismap={props.ismap}
917 usemap={props.usemap}
918 />
919 </span>
920 }
921 }
922 };
923 html! {
924 {layout}
925 }
926}