d

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore.

15 St Margarets, NY 10033
(+381) 11 123 4567
ouroffice@aware.com

 

KMF

How to Create iPhone Interface with Long Press in Javascript

Shaking icons and long presses have become something we are very familiar with from our phone screens. On iPhones in particular, shaking icons usually implies they are draggable and editable — while long presses have become the normal way to get additional options.

In this tutorial, we’ll be looking at recreating these effects in Javascript and CSS. In this tutorial, we will cover:

  • New CSS features — such as background blurs, and animated flex boxes.
  • Long pressing — how to create the long-press effect with Javascript.
  • Dragging — how to create a simple drag and drop system in Javascript.

Demo

As always, let’s start with the demo. This is what we’re planning to create today.

  • If you click and press on an icon for one second, a popup will flash up.
  • If you click and press for two seconds in total, the icons will begin to shake — just like on iPhone. This will also work on mobile.

Step 1: HTML

In this tutorial, I will not focus on the HTML too much as it is quite rudimentary — suffice to say the HTML for this demo is comprised of:

  • Icon Containers — a container div with all the information about individual icons.
  • A cover — a cover div that is on top of the entire demo, which will blur if the user long presses to show the sub-menu.
  • A few notifications — a few notifications at the bottom to give some instructions on what to do.
  • One submenu — one sub-menu which is moved around as the user clicks on various icons.

Step 2: CSS

Most of the CSS is quite basic, however, I will call out a few important and interesting things I have used which are pretty typical of HTML UI design. So our goal is to make the icons draggable, but there’s a lot in the way that can affect that. For one, we have a cover that pops up to blur out the background. Then we have those pesky images, which most browsers let users drag.

To get around most of these issues, we can use a property and value in CSS called pointer-events: none;. This means the user can’t interact with our HTML elements. So on the blurred cover, we remove pointer events when it’s not blurred, and add them on when the background blurs out. This means the user normally clicks through this HTML element, until we want to blur the background, at which point it becomes active:

#iphone .cover {    
    position: absolute;
    top: -1rem;
    backdrop-filter: blur(4px);
    left: -1rem;
    width: calc(100% + 2rem);
    opacity: 0;
    z-index: 99999;
    height: calc(100% + 2rem);
    transition: all 0.2s ease-out;
    background: rgba(0,0,0,0);
    pointer-events: none; /* No pointer events! */
}
[data-dropdown="true"] #iphone .cover {
    pointer-events: all; /* All pointer events */
    opacity: 1; 
}
#iphone .icon-container img {
    width: calc(100% + 2px);
    pointer-events: none; /* No pointer events */
}

Backdrop Filter

This brings us onto another CSS property that can be quite useful — backdrop-filter. With support in every major browser except Firefox, backdrop filters allow us to add effects to the elements behind an HMTL tag if the tag is slightly transparent. In our .cover CSS we have it defined as so:

backdrop-filter: blur(4px);

Animating Icon Removal

For this whole demo, I wanted icon removal to be smooth. The icons are arranged according to a flexbox. To do this I’ve created a custom animation which causes the icon to scale to effectively zero (so that it appears they zoom away)  —  and then reduces their width in tandem. The effect is that the icons seem to close in on each other as one is removed, creating a smooth animation.

This animation is applied in a small piece of Javascript when the user clicks on a removal button.

@keyframes scaleBack {
    0% {
        transform: scale(1);
        width: 5rem;
    }
    40% {
        opacity: 1;
    }
    50% {
        width: 5rem;
        opacity: 0;
        transform: scale(0.0001);
    }
    100% {
        width: 0rem;
        transform: scale(0.0001);
    }
}

Positives to Using Transforms

  • Lets you use top, bottom, left, and right positioning for other things.
  • Uses 3D acceleration, so it animates super fast!

Step 3: Javascript

The Javascript here is not super complicated — and in fact, a lot of the complexity comes from trying to make the icons draggable. There are essentially a couple of events we want to track:

  • When the user clicks on an icon — we want to measure for how long, so we can show the dropdown or the shaking effect.
  • When the user clicks off the button — we want to remove timers for long presses, and reset any other movement trackers.
  • When the user clicks the removal button — we want to remove that icon.
  • When the user clicks anything except a button — we want to remove the effects.

So, as you might have guessed, we use pointer events. I always recommend that you should try and use pointer-events if you can, since they are both mobile and desktop compatible. That way, you usually only need to write one script for all devices. Pointer Events replace event handlers like mouse down with pointerdown.

Tracking State

To track the user, we have two states — shaking, and dropdown. We use data attributes in the HTML tag to track this — if the icons are shaking then data-shaking="true" on the body tag, and if a dropdown is visible, then data-dropdown="true" is shown on the body tag. We do it this way so that we can change the CSS of the elements based on the state.

We then have the long press. To manage long presses, all we have to do is:

  • Create a pointerdown variable — this variable is set to true immediately when the user clicks, and it is set to false immediately when they release.
  • Create timeouts — we check after one second and two seconds to see if pointerdown is true. If it is, then we either cause the icons to show the dropdown or shake.
  • Set a reset function — We reset all variables should the user give up on the long press, so we can use them again if they try once more.

In code, this looks like this:

// For selection of the icons and removal icons
let icons = document.querySelectorAll('.icon-container');
let removals = document.querySelectorAll('.remove-icon');


// These all store data on the mouse position at different times in the code
let pointerdown = false;
let offset = 0;
let mouseXInit = 0;
let mouseYInit = 0;
let mouseX = 0;
let mouseY = 0;
let positionX = 0;
let positionY = 0;
let currentTop = 0;
let currentLeft = 0;

// This is for holding our timers
let timers = { first: undefined, second: undefined, third: undefined }

let helpers = {
    reset: function(extended) {
        // This is our reset - it sets everything back to zero, whenever we need to
        // All variables and settings are reset
        mouseX = 0;
        mouseY = 0;
        mouseXInit = 0;
        mouseYInit = 0;
        currentTop = 0;
        currentLeft = 0;
        offset = 0;
        if(typeof timers.first !== "undefined") {
            clearTimeout(timers.first);
        }
        if(typeof timers.second !== "undefined") {
            clearTimeout(timers.second);
        }
        if(typeof timers.third !== "undefined") {
            clearTimeout(timers.third);
        }
        if(typeof extended == "undefined") {
            document.querySelector('.sub-menu').classList.remove('show-sub-menu');
            document.body.setAttribute('data-shaking', false);
            document.body.setAttribute('data-dropdown', false);
            pointerdown = false;
            icons.forEach(function(item) {
                item.setAttribute('data-selected', false);
                item.style.top = 0;
                item.style.left = 0;
            })
        }
    },
    checkPoint: function(x, y, limit) {
        // This checks if the users mouse has moved more than a certain limit. If it has, then they may be dragging..
        // So we don't cause the long press animation
        if(x < limit && x > limit * -1 && y < limit && y > limit * -1) {
            return true;
        } else {
            return false;
        }
    }
}

// For every icon 
icons.forEach(function(item) {
    // Add a pointerdown event
    item.addEventListener('pointerdown', function(e) {
        // Get the click location and set pointerdown to true
        pointerdown = true;
        mouseXInit = e.pageX;
        mouseYInit = e.pageY;
        
        // Get the left and top position of the item, if any
        currentTop = parseFloat(item.style.top) || 0;
        currentLeft = parseFloat(item.style.left) || 0;
        // Set a timer to wait for a hold click
        timers.first = setTimeout(function() {
            // Only do this if pointerdown is true, and if the user hasn't moved more than 10px while clicking down
            if(pointerdown === true && document.body.getAttribute('data-shaking') !== "true" && helpers.checkPoint(mouseX, mouseY, 10)) {
                
                // Icon is now selected, and the dropdown should appear
                item.setAttribute('data-selected', true);
                document.body.setAttribute('data-dropdown', true);
                
                // Find out where exactly the icon is (x, y) coordinates
                let left = item.getBoundingClientRect().left - document.querySelector('#iphone').getBoundingClientRect().left;
                let bottom = item.getBoundingClientRect().bottom - document.querySelector('#iphone').getBoundingClientRect().top;
                // Show the sub menu and move it to where the icon is
                document.querySelector('.sub-menu').classList.add('show-sub-menu');
                document.querySelector('.sub-menu').style.left = `${left}px`;
                document.querySelector('.sub-menu').style.top = `${bottom - 16}px`;

            }
        }, 1000);
        // If the user is still clicking after 2 seconds
        timers.second = setTimeout(function() {
            // Check they are clicking
            if(pointerdown === true && helpers.checkPoint(mouseX, mouseY, 10)) {
                // Now all icons should shake
                document.body.setAttribute('data-shaking', true);
                item.setAttribute('data-dragging', true);
                // Hide the sub menu
                document.querySelector('.sub-menu').classList.remove('show-sub-menu');
                document.body.setAttribute('data-dropdown', false);
                // Give each animation for shaking a delay, to give the appearance of randomness
                timers.third = setTimeout(function() {
                    icons.forEach(function(i) {
                        i.style.animationDelay = `${offset}s`;
                        offset += 0.1;
                    })
                }, 300);
            }
        }, 2000);
        // If the icons are shaking, then the user may be trying to drag this particular icon. Set that icon
        // to have a data-dragging of true. We can use this later
        if(document.body.getAttribute('data-shaking') === "true") {
            item.setAttribute('data-dragging', true);
        }
    });
    // if the user lifts their mouse, then reset everything
    item.addEventListener('pointerup', function() {
        helpers.reset(false);
    });
})

Removal

When the user clicks an icon, then we have to animate that icon’s removal. To do that, we first add an animation that causes the icon to zoom away and reduce to a width of 0. We spoke about this in the previous CSS section. Then after the animation is over, we set another timeout to remove the icon entirely from the HTML.

removals.forEach(function(item) {
    item.addEventListener('click', function(e) {
        // If the removal icon is clicked, then get the parent HTML element - i.e. the icon itself
        let icon = item.parentNode;
        // Animate the icon to disappear
        icon.style.animation = 'scaleBack 0.4s linear 1 forwards';
        // Remove the dropdown, if it is around 
        document.body.setAttribute('data-dropdown', false);
        // And finally, delete the icon completely using the remove() function.
        setTimeout(() => {
            icon.remove();
        }, 400);
    })  
});

Draggability 

Next, let’s implement a basic form of dragging. Dragging conceptually can be broken down into a few pieces:

  • Firstly, when the user clicks on the page, we find out exactly where they clicked, using e.clientX, e.clientY. This gives us the coordinates of their click.
  • As they move with their pointer clicked down, we then find the difference between their new position, and that original click position. That difference is the total amount moved.
  • We then add that amount to the top and left CSS values for that icon. This gives us the drag effect. Eventually, if the user stops dragging, we reset all values so the icon snaps back to its original place.

Since we need to track quite a few things, we have a lot of variables. We can track the initial mouse position with mouseXInit, mouseYInit. Then, the difference is stored in positionX, positionY, after shaking is activated. We also store a separate mouseX, mouseY before shaking starts. If the user moves too much while clicking down, we don’t activate the long press effect, so we can check that with mouseX, mouseY.

In code, we end up with this:

document.body.addEventListener('mousemove', function(e) {
    // If the user is clicking down
    if(pointerdown === true) {
        // Track how much they're moving. If it's too much, we'll cancel the long press timeout
        mouseY = mouseXInit - e.pageY;
        mouseX = mouseXInit - e.pageX;
        if(document.body.getAttribute('data-shaking') == "true") {
            // If they are moving around after shaking starts, then they are dragging
            positionX = mouseXInit - e.pageX;
            positionY = mouseYInit - e.pageY;
            // Set the element to have a data-dragging attribute of true
            let el = document.querySelector('[data-dragging="true"]');
            if(el !== null) {
                // Move the element around
                el.style.top = `${positionY * -1 + currentTop}px`;
                el.style.left = `${positionX * -1 + currentLeft}px`;
            }
        }
    }
})

// When the user lifts their pointer up, then reset all the variables
document.body.addEventListener('pointerup', function(e) {
    if(!e.target.matches('.remove-icon')) {
        helpers.reset(false);
    }
    // And end all icon dragging by setting data-dragging to false on all icons.
    icons.forEach(function(item) {
        item.setAttribute('data-dragging', false);
    });
});

That about wraps it up for this tutorial. I hope you’ve enjoyed this, and maybe picked up a few new CSS skills. As always, here are some useful links:

Credit: Source link

Previous Next
Close
Test Caption
Test Description goes like this