I'm working on a Blazor project where I need to place objects (like bread, watermelon) on a background image. The problem is that when I move the objects, they appear underneath the image rather than on top of it where I want them. Here's an overview of my implementation and the problem:
Problem
I have a Blazor component where users can drag and drop objects onto a background image. However, the objects are not placed directly on the image but seem to appear underneath it when moved. I want the objects to be positioned exactly on the image, not beneath it.
Code
Here is the relevant part of my code:
@page "/planDisplay"@using siteplan_Projekt.Data@inject FileService fileService@inject IJSRuntime JsRuntime@rendermode InteractiveServer<PageTitle>Plan-Display</PageTitle>@if (errors.Count > 0){<ul class="text-danger"> @foreach (var error in errors) {<li>@error</li> }</ul>}<!-- siteplan-Projekt/Components/Pages/SitePlan/PlanDisplay.razor --><MudDropContainer T="DropItem" Items="_items" ApplyDropClassesOnDragStarted="_applyDropClassesOnDragStarted" ItemsSelector="@((item, dropzone) => item.Place == dropzone)" ItemDropped="ItemUpdated" Class="d-flex flex-column flex-grow-1"><ChildContent><div class="d-flex flex-wrap justify-space-between" @onmousemove="OnMouseMove"><MudDropZone T="DropItem" Identifier="ObjBox" CanDrop="@(item => false)" Class="rounded-lg border-2 border-solid mud-border-lines-default pa-6 ma-8"><MudText Typo="Typo.h6" Class="mb-4">Objekt</MudText></MudDropZone><div class="image-wrapper" style="position: relative; width: 800px; height: 600px;"><MudDropZone T="DropItem" Identifier="Image" CanDrop="@(item => item.IsPicked == false && item.IsRotten == false)" Class="site-plan-dropzone"><img src="@imageUpload[0].Url" alt="Site plan" class="site-plan" @onclick="onImageClick" style="width: 100%; height: 100%;"/></MudDropZone><svg class="grid-overlay" xmlns="http://www.w3.org/2000/svg" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"> @if (selectedPoints.Count == 1 && tempPoint != null) {<line x1="@selectedPoints[0].X" y1="@selectedPoints[0].Y" x2="@tempPoint.Value.X" y2="@tempPoint.Value.Y" stroke="red" stroke-width="3"/> } @if (selectedPoints.Count == 2) {<line x1="@selectedPoints[0].X" y1="@selectedPoints[0].Y" x2="@selectedPoints[1].X" y2="@selectedPoints[1].Y" stroke="red" stroke-width="3"/> } @if (selectedPoints.Count <= 2) { @foreach (var point in selectedPoints) {<circle cx="@point.X" cy="@point.Y" r="5" fill="blue"></circle> } }</svg></div></div></ChildContent><ItemRenderer><MudPaper Height="54px" Width="54px" Class="pa-2 icon-over-image" Elevation="0"><MudIcon Icon="@context.Icon"/></MudPaper></ItemRenderer></MudDropContainer>@if (selectedPoints.Count == 2){<MudItem xs="12"><MudTextField @bind-Value="realDistance" Label="Real Distance (meters)"/><MudButton OnClick="calculateScale">Calculate Scale</MudButton></MudItem>}<MudItem xs="12"><MudText>Scale: @scaleManager.getScale() pixels per meter</MudText></MudItem><MudToolBar><MudSpacer/><MudButton OnClick="Reset">Reset</MudButton></MudToolBar>using System.Drawing;using Microsoft.AspNetCore.Components;using Microsoft.AspNetCore.Components.Forms;using Microsoft.AspNetCore.Components.Web;using Microsoft.JSInterop;using MudBlazor;using siteplan_Projekt.Data;using siteplan_Projekt.Models;namespace siteplan_Projekt.Components.Pages.SitePlan;/// <summary>/// This class is responsible for displaying the plan./// It initializes and manages the display of uploaded images,/// and handles rendering behavior to ensure proper initialization./// </summary>public partial class PlanDisplay{ #region Protected /// <summary> /// Called when the component is initialized. /// Populates the `imageUpload` list with files retrieved from the `fileService`. /// </summary> protected override void OnInitialized() { try { imageUpload = fileService.GetUploadedFiles(); var dimensions = fileService.GetImageDimensions(imageUpload[0].Name); imageWidth = dimensions.width; imageHeight = dimensions.height; } catch (Exception e) { Console.WriteLine($"Error: {e.Message}"); } } /// <summary> /// Asynchronously called after the component has rendered. /// Executes actions that should only occur during the first render. /// If `firstRender` is true and `isFirstRender` is true, /// sets `isFirstRender` to `false` to prevent the first render /// logic from running again. /// </summary> /// /// /// <param name="CamBox"> /// True if this is the first time the component is rendered; /// false if it has been rendered before. /// </param> protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender && isFirstRender) isFirstRender = false; } #endregion #region Private private readonly List<PointF> selectedPoints = new(); private readonly Dictionary<string, object> svgAttributes = new(); private readonly List<PointF> objectPoints = new(); /// <summary> /// A list to store any errors encountered during processing. /// </summary> private readonly List<string> errors = new(); private readonly bool _applyDropClassesOnDragStarted = false; private readonly List<DropItem> _items = new() { new DropItem { Icon = Icons.Custom.Uncategorized.ChessKing, IsRotten = false, Place = "ObjBox" }, new DropItem { Icon = Icons.Custom.Uncategorized.Baguette, IsRotten = false, Place = "ObjBox" }, new DropItem { Icon = Icons.Custom.Uncategorized.Sausage, IsRotten = true, Place = "ObjBox" }, new DropItem { Icon = Icons.Custom.Uncategorized.WaterMelon, IsRotten = false, Place = "ObjBox" }, new DropItem { Icon = Icons.Custom.Uncategorized.Fish, IsRotten = true, Place = "ObjBox" } }; private PointF? tempPoint { get; set; } private string? imageUrl { get; set; } private double realDistance { get; set; } [Inject] private ScaleManager scaleManager { get; set; } private IBrowserFile? iBrowserFile; private int imageWidth; private int imageHeight; /// <summary> /// Indicates whether this is the first time the component is rendering. /// Initially set to true, it will be set to false after the first render. /// </summary> private bool isFirstRender = true; /// <summary> /// A list of image upload services used to manage uploaded images. /// </summary> private List<UploadedImageService> imageUpload = new(); private async Task onImageClick(MouseEventArgs e) { try { if (selectedPoints.Count < 2) { selectedPoints.Add(new PointF((float)e.OffsetX, (float)e.OffsetY)); } else { selectedPoints.RemoveAt(0); selectedPoints.Add(new PointF((float)e.OffsetX, (float)e.OffsetY)); } if (selectedPoints.Count == 2) { // Calculate the pixel distance between the two points var pixelDistance = calculatePixelDistance(selectedPoints[0], selectedPoints[1]); // Output the points for debugging purposes await JsRuntime.InvokeVoidAsync("console.log", $"Point 1: {selectedPoints[0]}, Point 2: {selectedPoints[1]}, Distance: {pixelDistance}px"); } objectPoints.Add(new PointF((float)e.OffsetX, (float)e.OffsetY)); StateHasChanged(); } catch (Exception ex) { await JsRuntime.InvokeVoidAsync("console.error", $"Error: {ex.Message}"); } } private double calculatePixelDistance(PointF point1, PointF point2) { return Math.Sqrt(Math.Pow(point2.X - point1.X, 2) + Math.Pow(point2.Y - point1.Y, 2)); } private void calculateScale() { if (selectedPoints.Count == 2 && realDistance > 0) { var pixelDistance = calculatePixelDistance(selectedPoints[0], selectedPoints[1]); scaleManager.scale = pixelDistance / realDistance; } } private async Task onFileSelected(IBrowserFile file) { iBrowserFile = file; var buffer = new byte[iBrowserFile.Size]; await iBrowserFile.OpenReadStream().ReadAsync(buffer); // Convert the uploaded image to a base64 string imageUrl = $"data:image/png;base64,{Convert.ToBase64String(buffer)}"; } private void OnMouseMove(MouseEventArgs e) { if (selectedPoints.Count == 1) { tempPoint = new PointF((float)e.OffsetX, (float)e.OffsetY); StateHasChanged(); } } private void Reset() { foreach (var item in _items) { item.Place = "ObjBox"; item.IsPicked = false; } } private void ItemUpdated(MudItemDropInfo<DropItem> dropItem) { dropItem.Item.IsPicked = true; dropItem.Item.Place = dropItem.DropzoneIdentifier; } #endregion}public class DropItem{ #region Public public string Icon { get; init; } public bool IsRotten { get; set; } public bool IsPicked { get; set; } public string Place { get; set; } #endregion}Questions
Why are the objects appearing underneath the image rather than directly on it?
How can I ensure that the objects are correctly positioned on the image?
I would greatly appreciate any help or guidance on how to resolve this issue. Thanks in advance!