Kotlin/JS Compose Multiplatform: HTML iframe Not R...
# compose-web
i
Kotlin/JS Compose Multiplatform: HTML iframe Not Rendering in jsMain Description: I’m working on a Kotlin/JS Compose Multiplatform project where I have multiple platform-specific modules: androidMain, commonMain, iosMain, jsMain, and desktopMain. My commonMain includes an entry point (App.kt) using the expect/actual mechanism for multiplatform composition. Inside jsMain, I’ve created a PlatformSpecificContent function to render platform-specific UI elements. The issue arises when I try to render an iframe inside jsMain. The Compose UI elements (such as Icon Buttons) display correctly, but the iframe I’m trying to insert does not render. Code Overview: JS Main (main.kt): fun main () { onWasmReady { CanvasBasedWindow("Compose Multiplatform") { App() } } } JS Main (platform.js.kt) : package com.telo.robotics.jsmain import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.size import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.Home import androidx.compose.material.icons.filled.Settings import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp class JSPlatform: Platform { override val name: String = "compose web using Kotlin/JS" } actual fun getPlatform(): Platform = JSPlatform() @Composable actual fun PlatformSpecificContent() { val platform = getPlatform().name if (platform == "compose web using Kotlin/JS") { Row { IconButton(onClick = { /* Handle Home click / }) { Icon(imageVector = Icons.Filled.Home, contentDescription = "Home", modifier = Modifier.size(24.dp)) } IconButton(onClick = { / Handle Favorite click / }) { Icon(imageVector = Icons.Filled.Favorite, contentDescription = "Favorite", modifier = Modifier.size(24.dp)) } IconButton(onClick = { / Handle Settings click */ }) { Icon(imageVector = Icons.Filled.Settings, contentDescription = "Settings", modifier = Modifier.size(24.dp)) } } Iframe(attrs = { // Set iframe attributes here attr("src", "index1.html") attr("width", "600") attr("height", "400") attr("style", "border: none;") }) } else { Text("Greeting does not match.") } } CommonMain (app.kt) : I’m calling this PlatformSpecificContent() My index.html: <!DOCTYPE html> html lang="en" head meta charset="UTF-8" titleKmpApp2/title script src="skiko.js"/script link type="text/css" rel="stylesheet" href="styles.css" /head body div canvas id="ComposeTarget" width="600" height="800"/canvas /div script src="composeApp.js"/script /body /html div /div Expected Behavior: The iframe should be rendered beside the row of Icon Buttons in jsMain. The HTML file content (from index1.html) should appear in the iframe. Actual Behavior: Only the row of Compose Icon Buttons is visible, and the iframe does not render at all. I’m unsure how the HTML mapping is being handled in Compose Multiplatform, and this issue seems to be specific to the jsMain platform. For other platforms like Android (which uses WebView) and Desktop (with a different library), the content renders fine. System Information: • Xcode Version: (e.g., 14.2) • macOS Version: (e.g., macOS Monterey 12.4) • Kotlin Multiplatform Plugin Version: Latest • Compose Multiplatform Version: Latest Library used : implementation("org.jetbrains.compose.htmlinternal html core runtime js1.7.0") Build.Gradle: js(IR) { moduleName = "composeApp" browser { commonWebpackConfig { outputFileName = "composeApp.js" } } binaries.executable() } Additional Information: I’m using the latest versions of Kotlin Multiplatform and Compose Multiplatform. Any insights into how to correctly map HTML iframes within Compose or potential workarounds for this issue would be greatly appreciated.
🧵 13
a
Can u format you code? very hard to understand what is going on
i
Yes of course , give me some time
s
Pull all the code as a reply on this thread, to avoid bloating the main chat. Also put all code in code blocks. And edit the original message to be keep just a few sentences that describe the problem.
3
i
Yes sure
Code Overview: JS Main (main.kt): fun main() { onWasmReady { CanvasBasedWindow("Compose Multiplatform") { App() } } } JS Main (platform.js.kt): class JSPlatform: Platform { override val name: String = "compose web using Kotlin/JS" } actual fun getPlatform(): Platform = JSPlatform() @Composable actual fun PlatformSpecificContent() { val platform = getPlatform().name if (platform == "compose web using Kotlin/JS") { Row { IconButton(onClick = { /* Handle Home click */ }) { Icon(imageVector = Icons.Filled.Home, contentDescription = "Home", modifier = Modifier.size(24.dp)) } IconButton(onClick = { /* Handle Favorite click */ }) { Icon(imageVector = Icons.Filled.Favorite, contentDescription = "Favorite", modifier = Modifier.size(24.dp)) } IconButton(onClick = { /* Handle Settings click */ }) { Icon(imageVector = Icons.Filled.Settings, contentDescription = "Settings", modifier = Modifier.size(24.dp)) } } Iframe(attrs = { // Set iframe attributes here attr("src", "index1.html") attr("width", "600") attr("height", "400") attr("style", "border: none;") }) } else { Text("Greeting does not match.") } } Common Main (App.kt): package com.telo.robotics.common import androidx.compose.runtime.Composable import androidx.compose.ui.window.ApplicationScope expect fun getPlatform(): Platform @Composable expect fun PlatformSpecificContent() // Main entry point for your app @Composable fun App() { ApplicationScope { // Render platform-specific content PlatformSpecificContent() } } Resources/assets index.html: <!DOCTYPE html> html lang="en" head meta charset="UTF-8" titleKmpApp2/title script src="skiko.js"/script link type="text/css" rel="stylesheet" href="styles.css" /head body div canvas id="ComposeTarget" width="600" height="800"/canvas /div script src="composeApp.js"/script /body /html Expected Behavior: The iframe should be rendered beside the row of Icon Buttons in jsMain. The HTML content from index1.html should appear in the iframe. Actual Behavior: Only the row of Compose Icon Buttons is visible, and the iframe does not render at all. It seems to be an issue specific to the jsMain platform, while other platforms like Android (with WebView) and Desktop render fine. System Information: • Xcode Version: e.g., 14.2 • macOS Version: e.g., macOS Monterey 12.4 • Kotlin Multiplatform Plugin Version: Latest • Compose Multiplatform Version: Latest Library Used: implementation("org.jetbrains.compose.htmlinternal html core runtime js1.7.0")
z
Slack has a code block format option that you should use to format your code. It uses a monospace font.
👆 2
s
Anyways, regarding your question, where does the
Iframe
(Composable) function come fromあ?? It's not built into Compose/Web, so did you make it yourself?
1
i
I’m new to here , where exactly I can find that feature , so please bear with me
It’s from the koltinx html core library Library Used: implementation("org.jetbrains.compose.htmlinternal html core runtime js1.7.0")
a
afaik there is no built in way to render iFrames on Compose Web The 'internal' part in your dependency seems suspicious and i doubt it does what you expect it does. Is that iframe even a composable (marked with the
@Composable
annotation)?
i
As per my understanding, I initially thought that they had converted the iframe into a Kotlin library. It also recognizes the iframe without throwing any errors. I believe if we already have an iframe inside the HTML, we can interact with it. I assumed this functionality was meant for that purpose, which is why I considered it as a Compose function. That’s why I’m here asking for clarification. I’m not entirely sure if my understanding is correct or if I’m going in the wrong direction. Could you please correct me if I’m mistaken? Additionally, if I want to achieve the same behavior (loading an HTML file), what would be the correct way to implement it inside Compose Multiplatform?
s
Ah, I see your problem now. You're mixing up kotlinx.html with Compose, but that's not how things work. Composables are special functions, and you'll need to use some library specially made to do this (I'm not sure if there's any native interop capability in Compose/Web, or if it's even possible to DIY it for now, things are still very early).
i
I now have a clearer understanding. I came across a library that allows the use of WebView inside desktop, iOS, and Android platforms. So, I thought a similar functionality to WebView could be achieved using an iframe in HTML, right? Anyway, I will try to create one myself and see if it works. If you come across any relevant resources or examples, feel free to refer them to me.
s
Surely! Note that in web, you don't even necessarily need an iframe, you can just have the content embedded directly in your page (e.g. some HTML-based sdk like maps..), since you're already in a web browser. But an iframe will give you behavior mostly consistent with other platforms, with isolation etc... But again, I don't know if interop with native HTML tags is even supported for now, so everything hinges on that. I'll take a look at this when I get to my computer.
i
Could you please take some time to look at this map library below: https://github.com/mapbox/mapbox-gl-js I’m looking to implement it on the web end of my project.
Yes of course , whenever you find time
It’s already been implemented for iOS, Android, and desktop using WebView. Someone created a cool library to make it work. However, I’ve gotten stuck on the web part.
s
I've seen someone on GitHub just recommend rendering the necessary HTML on top of the
<canvas>
where Compose draws (using
position: relative
) and making sure to sync up the item's position to your element's position in the Compose canvas. (you were there too :D)
I guess for now that's the only viable approach, but I can already foresee some drawbacks, mostly related to its interaction with animations/transitions. Animations that simply move/rotate the item around should be fine, but anything else (scaling, opacity...) not so much. Transitions are worse, because if you have a "fade out" for the screen where the native item is overlaid, its opacity won't change accordingly, until the screen disappears (though you can have a matching CSS transition to make it match the same underlying transition; at least to a certain degree). I'm not sure if there's a good way to deal with these for now, nor am I sure how native interop works on the Android, iOS and Desktop platforms, but if they don't have these issues, that means Compose somehow gets them to draw on the same canvas (impossible on web) or something.
Although I remember that in a KotlinConf talk about native interop on iOS, they mention that they kinda "cut" a portion of the canvas, and render the native elements underneath it. That could be done in web too, as the canvas can be made transparent, but I'm not sure how to get that working without those drawbacks either.
i
Was it you on the GitHub ? 🤝😂 I actually tried that approach the same day. The issue is, it’s affecting all the pages I design, and we can’t come to a solid conclusion with that method. The reason is that my iframe contains many elements, which are a crucial part of my application, and will handle things like latitude, longitude, and navigation in the future. If this were Android, I could easily manage it using a JavaScript interface. But I’m not sure how that works for this platform.
My brother and I have decided to create a cross-platform library, but we’re stuck with the web part since we have zero prior experience with how HTML is rendered in the browser. On Android, we’re familiar with running code on the JVM and interacting with the OS to make the app function. Right now, we know that on each platform, the canvas seems to be the primary way to render content. But for web, we’re uncertain about which canvas library is ideal or how the rendering works. Do you have any recommendations, or would you be interested in collaborating? We’re experienced in Java and Kotlin, especially at the kernel level, but when it comes to the web, we’re unfamiliar with the underlying architecture. I’ve learned a few basics, like how HTML is represented in a tree-like structure (DOM) and how nodes are styled with CSS. But, honestly, I find web development a bit frustrating – it feels overcomplicated! Initially, HTML handled everything: design, animation, etc. Now we have CSS, Bootstrap, JavaScript, React… 😂 On Android, iOS, or desktop, it’s straightforward – they all run on Linux and Java at the core. Right now, we’re in active brainstorming, trying to get a grip on things, but after a month, we’re still in the early stages. Any guidance would be really helpful!
So, when you mention the canvas, are you saying that half of the space is allocated to native Compose UI for rendering, while the other half is for HTML content? And if we don’t use that HTML UI, Compose UI will likely occupy the entire space itself? Let me know if I’m understanding correctly. Also, have you tried the approach you found on GitHub? How was your experience with it? Were you able to load the map I shared here?
s
I mean, web isn't too complicated, but it's quite "different" if you're coming from Android, and can be a bit confusing
There's a lot to unpack here; on web, there's HTML at the core of everything. Normally you'd write your code in JS, and create HTML elements, and style them with CSS. This is the usual web thing. Whatever framework/library/etc... that you use, they'll all get distilled into these basic building blocks. Now with Compose, we're still stuck inside HTML, but Compose handles its own drawing; it doesn't rely on
<p>
elements for text for example, nor use CSS for styling. All of that is done by Compose itself (through a rendering library called Skia), and all it wants is a place to draw its pixels.
i
So, you’re saying Skia is the rendering library that handles the UI logic coming from Compose and renders it inside the web? And if I want to achieve something similar, like how we have a Box in Compose, for example a div block in HTML, can I do that? For iframes, I know there’s nothing directly available so far, but what’s the ideal approach if we want to develop something similar? Also, I want to make sure I understand correctly. When using Box, Row, LazyColumn, Text or other Compose UI elements, what’s happening inside Skia is that they’re being converted into corresponding HTML tags, and then rendered. As you mentioned, whatever library we use, HTML is still the foundation, right? Let me know if I’m on the right track here!
s
Not really, it's not converting to HTML tags at all. That would be impossible. Instead, it just creates a single
<canvas>
HTML element, which provides a JavaScript API to draw into it (as well as using WebGL; which is what Skia does). That's where Compose draws its pixels (through Skia). Don't overthink Skia too much, it's just a rendering detail. All you should understand is that Compose doesn't rely on the browser for rendering, it's all custom rendering, into a single
<canvas>
element it places in the body of the HTML template that holds your page. This is the reason there are still issues with zoom etc... Compose has to handle all of that manually. You can open developer tools and you'll find the canvas element in the HTML node tree.
And so you got this single Canvas HTML element that contains your whole app, and you wanna add some other native elements (e.g. map). You essentially have two options: 1. Put it on top of the
<canvas>
2. Put it below the
<canvas>
, and make a "transparent" hole in the canvas for it to appear. All while synchronizing its size, position, and rotation to a corresponding node in Compose.
1
All you need to know for now: it's not very trivial. The best we can hope for is proper support from JetBrains themselves. And even then there may still be limitations, due to the nature of the DOM.
i
Yeh that’s all we can do the least
Yeh can you give me sample code to play around as we are new to web especially when you talk about those two options to achieve it Any assistance would be appreciated
s
I'm a bit low on time these days, but I'll try to find some time to take a stab at it. It's an interesting experiment for sure :D
🤝 1
i
Thanks for your support I’ll keep you posted
205 Views