Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iOS context menu does not work #219

Open
dirkwa opened this issue Dec 13, 2024 · 11 comments
Open

iOS context menu does not work #219

dirkwa opened this issue Dec 13, 2024 · 11 comments

Comments

@dirkwa
Copy link

dirkwa commented Dec 13, 2024

I run the app on iOS and ipadOS full screen after adding it to my home screen. What I miss is to press and hold on the chart to get a direct goto option.

Also the ability to hold and tab the orange dst line to delete it.

@dirkwa
Copy link
Author

dirkwa commented Jan 19, 2025

On iOS I have no ability to reach the right-mouse-botton context menu.

@panaaj
Copy link
Member

panaaj commented Jan 20, 2025

I don't have IOS devices but the context menu works fine on Android devices. I assume you are just using Safari to load the web page.... are you loading it in fullscreen or just within the browser?

Image

@dirkwa dirkwa changed the title Feature request: GOTO iOS context menu does not work Jan 20, 2025
@dirkwa
Copy link
Author

dirkwa commented Jan 20, 2025

For me it does not work in safari or in the fullscreen app. Tested on iphone and ipad.

@dirkwa
Copy link
Author

dirkwa commented Jan 20, 2025

@panaaj
Copy link
Member

panaaj commented Jan 20, 2025

The cause seems to be that Safari on iOS does not support the contextmenu event.

@dirkwa
Copy link
Author

dirkwa commented Jan 20, 2025

Hmm. I just tried google chrome on iOS, there is also no contextmenu.

@dirkwa
Copy link
Author

dirkwa commented Jan 20, 2025

From ChatGPT:

Here’s a step-by-step implementation of a context menu that works on the latest iPadOS and iOS. The menu will appear when the user taps and holds an area on the website.

Full Example Code:

<title>Custom Context Menu</title> <style> body { margin: 0; padding: 0; height: 100vh; display: flex; align-items: center; justify-content: center; background-color: #f5f5f5; }
.context-menu {
  position: absolute;
  display: none;
  background-color: #ffffff;
  border: 1px solid #ddd;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
  border-radius: 8px;
  z-index: 1000;
  overflow: hidden;
}

.context-menu .menu-item {
  padding: 12px 16px;
  font-size: 16px;
  color: #333;
  cursor: pointer;
  text-align: center;
}

.context-menu .menu-item:hover {
  background-color: #f0f0f0;
}

.trigger-area {
  width: 300px;
  height: 200px;
  background-color: #007bff;
  color: white;
  display: flex;
  justify-content: center;
  align-items: center;
  border-radius: 8px;
  user-select: none;
  text-align: center;
}
</style>
Tap and Hold Here
Option 1
Option 2
Option 3
<script> const triggerArea = document.querySelector('.trigger-area'); const contextMenu = document.getElementById('context-menu'); let touchTimer; // Function to show the context menu function showContextMenu(x, y) { contextMenu.style.left = `${x}px`; contextMenu.style.top = `${y}px`; contextMenu.style.display = 'block'; } // Function to hide the context menu function hideContextMenu() { contextMenu.style.display = 'none'; } // Handle touch and hold for iOS and iPadOS triggerArea.addEventListener('touchstart', (event) => { const touch = event.touches[0]; touchTimer = setTimeout(() => { showContextMenu(touch.clientX, touch.clientY); }, 500); // Trigger after 500ms of holding }); triggerArea.addEventListener('touchend', () => { clearTimeout(touchTimer); }); // Handle context menu for desktop users (right-click) triggerArea.addEventListener('contextmenu', (event) => { event.preventDefault(); // Disable the default context menu showContextMenu(event.clientX, event.clientY); }); // Hide menu on outside tap or click document.addEventListener('click', (event) => { if (!contextMenu.contains(event.target)) { hideContextMenu(); } }); // Prevent accidental scrolling while holding triggerArea.addEventListener('touchmove', () => { clearTimeout(touchTimer); }); </script>

How It Works:
1. HTML:
• A trigger-area is created where the user can tap and hold to show the menu.
• A hidden context-menu div holds the menu options.
2. CSS:
• The context-menu is styled as a pop-up with a clean, modern design.
• Hidden by default (display: none) and positioned dynamically when triggered.
3. JavaScript:
• Touch and Hold: A touchstart event starts a timer. If the user holds for 500ms without lifting (touchend) or moving (touchmove), the menu pops up at the touch location.
• Right-Click Support: A contextmenu event handles desktop users and prevents the default context menu.
• Close Menu: If the user clicks or taps outside the menu, it will close automatically.
• Prevent Scrolling During Hold: Cancels the touch action if the user moves their finger while holding.

Features:
• Mobile-Friendly: Works well on iOS, iPadOS, and touch devices.
• Desktop Support: Handles right-click for non-touch users.
• Customizable: Easily extend the menu items or adjust styles.

This implementation ensures the menu is intuitive for touch users and works seamlessly across modern iOS and desktop environments. Let me know if you need further refinements!

@dirkwa
Copy link
Author

dirkwa commented Jan 30, 2025

On iOS Safari, long-press gestures often trigger system-level interactions rather than custom context menus defined in JavaScript. Unlike Android, which generally allows right-click-like context menus on long-presses, iOS Safari requires some specific adjustments to enable them.

Possible Fixes:

Here are some approaches to ensure that your custom context menu works on iOS Safari:

  1. Prevent Default Gesture Handling

By default, Safari may interpret long-presses as actions for system menus (e.g., image previews). To override this behavior, you should prevent the default touch action.

document.addEventListener("contextmenu", function (event) {
event.preventDefault(); // Prevent default Safari menu
});

  1. Use touchstart and touchend for Custom Context Menus

Since contextmenu events do not always work reliably on iOS, use touchstart and touchend to detect long-press gestures.

let touchTimer;

document.addEventListener("touchstart", function (event) {
touchTimer = setTimeout(() => {
showCustomMenu(event);
}, 500); // Adjust delay as needed
});

document.addEventListener("touchend", function () {
clearTimeout(touchTimer);
});

function showCustomMenu(event) {
event.preventDefault(); // Prevent default behavior

const menu = document.getElementById("customMenu");
menu.style.display = "block";
menu.style.top = `${event.touches[0].clientY}px`;
menu.style.left = `${event.touches[0].clientX}px`;

}

This approach ensures that a long press (around 500ms) triggers the custom menu while ignoring quick taps.

  1. Ensure Elements Are Not Blocking Touch Events

Some CSS rules can interfere with touch interactions:

  • {
    -webkit-touch-callout: none; /* Prevents the default iOS menu /
    -webkit-user-select: none; /
    Prevents text selection */
    }
  1. Use pointerdown Instead of touchstart (iOS 15+)

Since iOS 15+, pointerdown is a better alternative for detecting user interactions across devices:

document.addEventListener("pointerdown", function (event) {
touchTimer = setTimeout(() => {
showCustomMenu(event);
}, 500);
});

document.addEventListener("pointerup", function () {
clearTimeout(touchTimer);
});

Conclusion

To ensure context menus work reliably in iOS Safari:
• Use touchstart + touchend to detect long-presses.
• Prevent default gestures that interfere with your menu.
• Adjust CSS to avoid Safari’s system interactions.
• Consider using pointerdown for a more universal solution.

Would you like a working demo with full HTML/CSS/JS?

@dirkwa
Copy link
Author

dirkwa commented Jan 30, 2025

Here’s a fully functional HTML, CSS, and JavaScript implementation of a custom context menu that works on iOS Safari, Android, and desktop browsers.

Key Features

✅ Supports iOS Safari (Long Press)
✅ Works on Android and Desktop (Right Click)
✅ Uses touchstart & pointerdown for compatibility
✅ Prevents default browser context menus
✅ Smooth positioning of the custom menu

Full Code Example

<title>Custom Context Menu</title> <style> body { font-family: Arial, sans-serif; height: 100vh; display: flex; align-items: center; justify-content: center; background-color: #f4f4f4; -webkit-touch-callout: none; /* Prevent iOS default menu */ -webkit-user-select: none; /* Disable text selection */ }
    /* Context Menu Styles */
    .custom-menu {
        display: none;
        position: absolute;
        background: white;
        border: 1px solid #ccc;
        box-shadow: 2px 2px 5px rgba(0,0,0,0.2);
        z-index: 1000;
        padding: 8px;
        border-radius: 5px;
        min-width: 150px;
    }

    .custom-menu ul {
        list-style: none;
        padding: 0;
        margin: 0;
    }

    .custom-menu ul li {
        padding: 10px;
        cursor: pointer;
        border-bottom: 1px solid #ddd;
    }

    .custom-menu ul li:last-child {
        border-bottom: none;
    }

    .custom-menu ul li:hover {
        background-color: #eee;
    }
</style>
<div id="contextArea" style="width: 90%; height: 90%; background: white; border: 2px dashed #999; text-align: center; padding-top: 20%;">
    <h3>Long press or right-click to open the custom menu</h3>
</div>

<!-- Custom Context Menu -->
<div id="customMenu" class="custom-menu">
    <ul>
        <li onclick="alert('Option 1 selected')">Option 1</li>
        <li onclick="alert('Option 2 selected')">Option 2</li>
        <li onclick="alert('Option 3 selected')">Option 3</li>
    </ul>
</div>

<script>
    let touchTimer;
    const contextArea = document.getElementById("contextArea");
    const customMenu = document.getElementById("customMenu");

    // Show Custom Menu
    function showCustomMenu(event) {
        event.preventDefault(); // Prevent default menu

        // Position menu
        const x = event.touches ? event.touches[0].clientX : event.clientX;
        const y = event.touches ? event.touches[0].clientY : event.clientY;

        customMenu.style.display = "block";
        customMenu.style.left = `${x}px`;
        customMenu.style.top = `${y}px`;
    }

    // Hide Custom Menu
    function hideCustomMenu() {
        customMenu.style.display = "none";
    }

    // Prevent Default Context Menu
    document.addEventListener("contextmenu", (event) => {
        event.preventDefault();
        showCustomMenu(event);
    });

    // Long Press Detection (iOS & Android)
    contextArea.addEventListener("pointerdown", (event) => {
        touchTimer = setTimeout(() => {
            showCustomMenu(event);
        }, 500); // Adjust long press delay
    });

    contextArea.addEventListener("pointerup", () => clearTimeout(touchTimer));
    contextArea.addEventListener("pointerleave", () => clearTimeout(touchTimer));

    // Hide Menu on Click Outside
    document.addEventListener("click", hideCustomMenu);
</script>

How It Works
1. Right-click (Desktop) → Custom menu appears.
2. Long press (500ms) (iOS/Android) → Custom menu appears.
3. Click anywhere outside → Custom menu disappears.
4. CSS prevents default iOS touch menu (-webkit-touch-callout: none).

Test It Yourself
• Open in iOS Safari → Long press to open the menu.
• Open in Android Chrome → Long press works.
• Open in Desktop Chrome, Firefox, Edge → Right-click works.

Would you like additional customization, such as animations or touch gestures? 🚀

@dirkwa
Copy link
Author

dirkwa commented Jan 30, 2025

I tried the last code here https://codepen.io/pen and it worked on iOS 😁

image

@panaaj
Copy link
Member

panaaj commented Jan 31, 2025

I am working on a fix that does not impact map operations such as panning, etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants