Dragging to Scroll with JavaScript

My portfolio has some cards to showcase projects and blog posts. On mobile, these cards display in a horizontal slider, which is easy enough to scroll on a touchscreen, or trackpad, but what if someone is viewing the website at a small size on a device with a mouse? Well they can of course use the circular buttons below the cards, but I wanted to give these users an experience the same as on a touchscreen, allowing them to drag and scroll the card list.

I’ve used the vue-dragscroll library before on another project, but fancied a challenge of doing it myself for my portfolio.

I came across this article, and adapted the code to fit my use-case.

The code

.scroll-container {
  display: grid;
  column-gap: 10px;
  grid-auto-flow: column;
  // We set the grid colums here, a gutter each side, then I have 6 cards so I use the grid repeat function to make 6 equal width columns. The columns are 100vw minus the left and right gutter, and minus the column gap we set above
  grid-template-columns: 30px repeat(6, calc(100vw - 80px)) 30px;
  // We want to allow the cards to overflow horizontally
  overflow-x: auto;
  padding: 0;

  // This allows snapping to each card, so we don't get stuck half over one card and half over another. https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-type
  scroll-snap-type: x mandatory;
}
.drag-scroll--enabled {
  cursor: grab
}
.drag-scroll--scrolling {
  cursor: grabbing;
  user-select: none;
  // We set the scroll-snap-type to none here to allow for a more natural experience with dragging and scrolling. If we didn't, you wouldn't see any indication that you are scrolling the container
  scroll-snap-type: none
}
<div ref="container" class="row grid scroll-container">
  <div class="card">
    ...
  </div>
</div>
export default {
  data () {
    return {
      position: {
        left: 0,
        x: 0
      }
    }
  },
  mounted () {
    this.dragScrollWatcher()
    // We want to listen to the resize listener here to enable/disable the drag to scroll functionality depending on the layout of the page - for example, on my site, the cards are only in a horizontal slider below the 768px breakpoint. I chose to handle this with CSS in case I want to use these functions elsewhere, rather than having these breakpoints set in the JS
    window.addEventListener('resize', this.dragScrollWatcher)
  },
  beforeDestroy () {
    // We want to clear up any event listeners when we switch pages
    this.stopDragScroll()
    window.removeEventListener('resize', this.dragScrollWatcher)
  },
methods: {
    dragScrollWatcher () {

      // We only want to start drag scroll if the following conditions are met
      if (!this.hasTouchScreen() && this.hasOverflowAuto()) {
        this.startDragScroll()
      } else {
        this.stopDragScroll()
      }
    },
    startDragScroll () {

      // We set a listener for mousedcown so we know when to start the drag and scroll
      document.addEventListener('mousedown', this.mouseDownHandler)
      //
 We set this class on the container to allow the CSS to set some styles such as the cursor: grab
      this.$refs.container.classList.add('drag-scroll--enabled')
    },
    stopDragScroll () {
      document.removeEventListener('mousedown', this.mouseDownHandler)
      this.$refs.container.classList.remove('drag-scroll--enabled')
      // This clears up some event listeners and resets our classes
      this.mouseUpHandler()
    },
    hasTouchScreen () {

      // If this is a touch device, scrolling is already easy, so we don't need to enable our drag scroll feature
      return ('ontouchstart' in window)
    },
    hasOverflowAuto () {
      /*
        Rather than worrying about breakpoints here, we let CSS handle it, as they may be different for each component
        If overflow-x: auto is not on the element, then it is not a scrolling element, so we don't need to run DragToScroll
      */
      return (getComputedStyle(this.$refs.container).getPropertyValue('overflow-x') === 'auto')
    },
    mouseDownHandler (e) {

      // We set a class here to let the CSS know that we are currently scrolling, and to apply the relevant styles, such as the grabbing cursor
      this.$refs.container.classList.add('drag-scroll--scrolling')

      this.position = {
        // The current scroll
        left: this.$refs.container.scrollLeft,
        // Get the current mouse position
        x: e.clientX
      }

      // We want to listen to the mouse move so we know how much to scroll the container
      document.addEventListener('mousemove', this.mouseMoveHandler)

      // We want to know when to stop dragging and scrolling
      document.addEventListener('mouseup', this.mouseUpHandler)
    },
    mouseMoveHandler (e) {
      // How far the mouse has been moved
      const dx = e.clientX - this.position.x

      // Scroll the element
      this.$refs.container.scrollLeft = this.position.left - dx
    },
    mouseUpHandler () {

      // We don't care about listening to the mouse moving now, so we can remove the listener
      document.removeEventListener('mousemove', this.mouseMoveHandler)
      // We've just fired this listener, so no need to fire it again
      document.removeEventListener('mouseup', this.mouseUpHandler)

      // We can now remove the class which means we don't show the styles specific to when we are scrolling
      this.$refs.container.classList.remove('drag-scroll--scrolling')
    }
  }
}

How it looks

Native touch scroll

Dragging to Scroll with JavaScript

I often find articles and need some further context before I can adapt them, so all of the source code of my site is available on GitHub for you to view. You can always contact me if you need further help.

How To Secure your server using UFW and Cloudflare

This guide will help you use UFW (uncomplicated firewall) to block all traffic to your server other than traffic coming via Cloudflare, or for essential operations such as FTP and SSH.

Why would you want to do this?

Cloudflare will protect your site from all manner of attacks. However, your server may still be vulnerable. By closing ports down to everyone but CloudFlare, you will hopefully protect yourself further. Cloudflare offers Argo as a solution to combat this, which effectively does the same thing, blocks all traffic except Cloudflare’s. Argo costs at least $5 a month per domain. UFW is free, however, does nothing against DDOS attacks. You’ll want to make sure that the DNS Zones for websites hosted on the server we’re about to lock down are protected by Cloudflare, i.e. have the orange cloud rather than the grey one.

How can I set up UFW?

First, you’ll need to be running Debian or Ubuntu. I recommend (and use) DigitalOcean. For $100 free credit over 60 days, sign up using this link (I get $25 when you have spent $25).

UFW is usually installed by default. You can check using:

sudo ufw status

If it isn’t installed you can install it by running this command:

sudo apt-get install ufw

Before anything else, make sure you won’t lock yourself out, by opening up your SSH port:

sudo ufw allow ssh

If your SSH is not running on the default port 22, then run:

sudo ufw allow 1234/tcp

Replace 1234 with the SSH port number.

Now allow anything else that runs on your server, like FTP:

sudo ufw allow ftp

But not the web ports (80, 443) – we’re getting to that.

Cloudflare publishes the IP addresses of its servers online.

Without further ado, copy and paste the following into your terminal:

This allows all connections from <ip address>, to anything on port <port>.

sudo ufw allow from 173.245.48.0/20 to any port http
sudo ufw allow from 103.21.244.0/22 to any port http
sudo ufw allow from 103.22.200.0/22 to any port http
sudo ufw allow from 103.31.4.0/22 to any port http
sudo ufw allow from 141.101.64.0/18 to any port http
sudo ufw allow from 108.162.192.0/18 to any port http
sudo ufw allow from 190.93.240.0/20 to any port http
sudo ufw allow from 188.114.96.0/20 to any port http
sudo ufw allow from 197.234.240.0/22 to any port http
sudo ufw allow from 198.41.128.0/17 to any port http
sudo ufw allow from 162.158.0.0/15 to any port http
sudo ufw allow from 104.16.0.0/12 to any port http
sudo ufw allow from 172.64.0.0/13 to any port http
sudo ufw allow from 131.0.72.0/22 to any port http

If you also use IPv6, also copy the following:

sudo ufw allow from 2400:cb00::/32 to any port http
sudo ufw allow from 2606:4700::/32 to any port http
sudo ufw allow from 2803:f800::/32 to any port http
sudo ufw allow from 2405:b500::/32 to any port http
sudo ufw allow from 2405:8100::/32 to any port http
sudo ufw allow from 2a06:98c0::/29 to any port http
sudo ufw allow from 2c0f:f248::/32 to any port http

If you use HTTPS, also do the following:

sudo ufw allow from 173.245.48.0/20 to any port https
sudo ufw allow from 103.21.244.0/22 to any port https
sudo ufw allow from 103.22.200.0/22 to any port https
sudo ufw allow from 103.31.4.0/22 to any port https
sudo ufw allow from 141.101.64.0/18 to any port https
sudo ufw allow from 108.162.192.0/18 to any port https
sudo ufw allow from 190.93.240.0/20 to any port https
sudo ufw allow from 188.114.96.0/20 to any port https
sudo ufw allow from 197.234.240.0/22 to any port https
sudo ufw allow from 198.41.128.0/17 to any port https
sudo ufw allow from 162.158.0.0/15 to any port https
sudo ufw allow from 104.16.0.0/12 to any port https
sudo ufw allow from 172.64.0.0/13 to any port https
sudo ufw allow from 131.0.72.0/22 to any port https

And for HTTPS and IPv6:

sudo ufw allow from 2400:cb00::/32 to any port https
sudo ufw allow from 2606:4700::/32 to any port https
sudo ufw allow from 2803:f800::/32 to any port https
sudo ufw allow from 2405:b500::/32 to any port https
sudo ufw allow from 2405:8100::/32 to any port https
sudo ufw allow from 2a06:98c0::/29 to any port https
sudo ufw allow from 2c0f:f248::/32 to any port https

Run the following to enable UFW:

sudo ufw enable

Run this to check UFW’s status:

sudo ufw status

Now, nobody can access your websites without going through Cloudflare. And nobody can access your server through ports which don’t have ALLOW written next to them.

Adobe XD Challenge Calculator

The third challenge was to create a calculator. I wanted to practise my CSS art and so chose an image of an old Casio calculator and tried to recreate it as closely as possible. It was quite a challenge creating the code for the calculator to function. It wasn’t as simple as just adding numbers together. Each key had to be mapped to it’s own function.

Adobe XD Challenge Fitness App

The second challenge was to create a fitness app. I chose to make an activity tracker. I took inspiration from Lifelog, an app by Sony that I used for many years. I used the Google Maps API and created a component in Vue for the activities.

Southampton Focus

Southampton Focus is the online place to go for finding businesses in and around the area. Businesses pay for a listing on the site and get free access to Southampton Focus’ vast social network presence.

Southampton Focus already had a site but decided to scrap it and start completely afresh. They chose a theme from Theme Forest and I configured WordPress and the theme to their specifications. I had to customize the theme quite a bit to get it to the functionality that was advertised on Theme Forest, and we used many different plugins together to give the site company listings functionality.

Overall, I’m really pleased with how it turned out considering the scope of the site.

Stakks Pancake House

This Pancake House in Southampton wanted a complete website redesign. They got Mirror Digital Media to design the site and I built a custom WordPress theme from the designs. I then migrated all of the old blog posts to the new website and made a couple of small design changes that weren’t finalized in the initial design before pushing the website live. I used Local by FlyWheel to develop the site but there was a lot of hassle with sending an ngrok link every day for the client to view the site. In the future, I’m just going to create sites under a subdomain on my Digital Ocean Droplet and work on them there so that my client will be able to access them at all times.