What's a good way to responsively layer images on a Canvas object?

I’ve made a couple games with a board made from an image, displayed via a Canvas object. Additional Image or Link objects are the loaded on top of the board image.

I’m trying to make the games responsive/adaptive to different screen sizes. I use JavaScript to find the displayed window size and during __init__ call a method based on the window size. Here’s an example:

  def mobile_screen_dimensions(self):
    flag_img_path="_/theme/green_check_mark_small.png" if self.player_color=="green" else "_/theme/blue_check_mark_small.png"
    self.images = {
      "board": URLMedia("_/theme/sequence_board_320.png"),
      "flag": URLMedia(flag_img_path),
      "green_chip": URLMedia("_/theme/chipGreen_border_small.png"),
      "blue_chip": URLMedia("_/theme/chipBlue_border_small.png"),
    }
    self.IMAGE_WIDTH = 28
    self.IMAGE_HEIGHT = 28
    self.CANVAS_WIDTH = 288
    self.CANVAS_HEIGHT = 288
    self.canvas_size = self.CANVAS_WIDTH
    self.canvas_1.height = self.CANVAS_HEIGHT
    # code setting font sizes ...

Even so, I’m constantly playing with x and y locations to display flag, green_chip, and blue_chip images where I want them to be in various screen sizes.

In my next game attempt I used an xypanel for displaying the game board and other components:

class Form1(Form1Template):
  
  def __init__(self, **properties):
    global first_form_show # have to do this because we're changing value of variable; without global declaration Python thinks it's a local variable
    # Set Form properties and Data Bindings.
    self.init_components(**properties)

    # Check screen width
    _mobile_screen_width = 600
    _tablet_screen_width = 850
    window_size=window.innerWidth
    mobile_screen = window_size <= _mobile_screen_width
    tablet_screen = _mobile_screen_width < window_size <= _tablet_screen_width
    desktop_screen = window_size > _tablet_screen_width
    
    true_img_width,true_img_height=anvil.image.get_dimensions(URLMedia('_/theme/boggle_board.jpg'))
    disp_mode="shrink_to_fit" if mobile_screen else "original_size"
    
    board_img=Image(source='_/theme/boggle_board.jpg',display_mode=disp_mode)
    self.xypanel = XYPanel(align='right')
    self.xypanel.add_component(board_img,0,0)

    if mobile_screen:
      board_img.height=window_size*true_img_height/true_img_width
      self.xypanel.height=board_img.height

    # This is a row with textboxes and buttons
    self.enter_word_row=self.make_enter_word_row(mobile_screen)

    if mobile_screen:
      self.grid_panel_1.add_component(self.xypanel,row=1,col_xs=0,width_xs=12)
      self.grid_panel_1.add_component(self.enter_word_row, row=2, col_xs=0, width_xs=12)
      self.grid_panel_1.add_component(self.lst_words,row=3,col_xs=0,width_xs=12)
    elif tablet_screen:
      self.grid_panel_1.add_component(self.xypanel,row=1,col_xs=0,width_xs=9)
      self.grid_panel_1.add_component(self.lst_words,row=1,col_xs=8,width_xs=3)
      self.grid_panel_1.add_component(self.enter_word_row, row=2, col_xs=0, width_xs=12)
    elif desktop_screen:
      self.grid_panel_1.add_component(self.xypanel, row=1, col_xs=0, width_xs=6)
      self.grid_panel_1.add_component(self.lst_words, row=1, col_xs=6, width_xs=6)
      self.grid_panel_1.add_component(self.enter_word_row, row=2, col_xs=0, width_xs=12)

This code works pretty well for adapting the display to different screens. The struggle comes while trying to display links where I want them on top of the image:

self.img_width=window_size if mobile_screen else true_img_width
self.img_height=board_img.height if mobile_screen else true_img_height
self.font_size=40 if mobile_screen else 50
self.make_board(board_number,self.img_width,self.img_height,self.font_size)

The make_board method adds the links (which I’m referring to as “tiles”), from an array, via code including:

# Coordinates for link positions in grid:
x_positions = [0.08, 0.315, 0.55, 0.77]
y_positions = [0.01, 0.26, 0.52, 0.76]
for y in self.y_positions:
      for x in self.x_positions:
        link = self.create_link(tile_array.pop(), font_size )
        self.link_array.append(link)
        self.xypanel.add_component(link,x=x*img_width,y=y*img_height)

I’ve decided–like they say on informercials–there’s got to be a better way! … Is there? Maybe something involving relative sizes and positions? Different layout components? A magic wand?

Yeah, I had similar issues with dealing with canvas sizes and placing objects. I ended up using a ratio of screen dimensions for everything. I also reoriented the coordinate system to the bottom left because the top-left origin was driving me nuts.

For my application I didn’t need to maintain the aspect ratios of objects so that might be something you will need to noodle over. But RatioCanvas within Matrial Design Canvas might be helpful.

For overlapping issues, I was creating the elements for the canvas with a z-height then ordering, and rendering them. In my case, I was doing this to cast shadows, but you could use a similar approach without the extra step of rendering shadows. You can see this approach in MaterialCanvas. Take a look at the demos for some usage examples.

elevation_demo

1 Like

Perhaps Transformation methods?

In response to another question about placing components at specific coordinates, I put together a simple example to demonstrate how a canvas can handle everything—from rendering and text to mouse events.

My advice then, and now to you, is: don’t go down that path.

Trying to precisely control the position of standard components usually leads to frustration. They’re built for simplicity and convenience, not for fine-tuned control.

In many cases, getting components to behave exactly as needed takes more time and effort than starting from scratch on a canvas. It’s already a challenge using HTML/CSS/JS for pixel-perfect layouts, and even more so within Anvil, which adds another layer of abstraction that distances you from the underlying components.

You can find my example here:

Thank you, everyone! Something just came up that’s going to delay me looking at all this useful info for a week or two but, rest assured, I will be studying it all carefully. I’ll be experimenting with the scale(x,y) method and learning from the code samples you’ve provided. … This is a helpful, friendly community. I really like it.