Skip to content

Commit

Permalink
Merge pull request #7 from jwanner83/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
jwanner83 authored Jun 15, 2020
2 parents f98b82b + 0bdc3cd commit ca735d3
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 121 deletions.
16 changes: 5 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# follow-js
A experimental, dependency free script that let elements follow your cursor without a huge overhead.
An experimental, dependency free script that let elements follow your cursor without a huge overhead.

## Usage
1. Include the `follow.min.js` script to your project
Expand All @@ -13,24 +13,18 @@ leave it empty, it is set to 10.
2. `<script src"node_modules/follow-js/dist/follow.min.js"></script>`

### unpkg
1. `<script src="https://unpkg.com/follow-js@1.1.2/dist/follow.min.js"></script>`
1. `<script src="https://unpkg.com/follow-js@2.0.0/dist/follow.min.js"></script>`

## Options
### Dynamically add new elements
If you want to add a new element with the follow animation, fire the `follow.destroy()` method, which resets all the
elements to its initial position. Then add the new element and fire `follow.init()` to re initialize the script.

## Special behaviours
### position relative container
If you have an element which is inside an `position: relative` container (as you can see in the examples in
`relative-container.html`) the element will be hidden from the inside of the container and copied to the body. It will
stay at the exact position on the page as before but will correctly follow the cursor. This might generate some problems
if you would like to have a special styling for those.
### Destroy
If you want to destroy the follow animations, use the `follow.destroy()` method. As parameter, you can set the duration
of the animation which sets the elements back to their initial position. Default is `300`

## Restriction
### transition
In the current state, the script won't work properly with a transition on the element.

### transform: translate(-50%, -50%)
The transform property won't currently work because this one calculates the element completely different from we the way
we do which would require a complete rewrite of the script.
Expand Down
2 changes: 1 addition & 1 deletion dist/follow.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions examples/basic.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
<title>Basic Example - follow.js</title>

<style>
html, body {
height: 100%;
width: 100%;
margin: 0;
}

div {
position: absolute;

Expand Down
1 change: 0 additions & 1 deletion examples/container.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
}

.item {
position: absolute;
height: 100px;
width: 100px;
background: black;
Expand Down
1 change: 1 addition & 0 deletions examples/exact.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
body, html {
height: 100%;
width: 100%;
margin: 0;
}

body {
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"name": "follow-js",
"version": "1.1.2",
"version": "2.0.0",
"description": "let elements follow your cursor",
"main": "dist/follow.js",
"main": "dist/follow.min.js",
"repository": {
"type": "git",
"url": "git+https://github.com/jwanner83/follow-js.git"
Expand All @@ -12,7 +12,8 @@
"js-animation",
"css-animation",
"ui",
"design"
"design",
"frontend"
],
"author": "Jonas Wanner",
"license": "ISC",
Expand Down
78 changes: 59 additions & 19 deletions src/helper.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,67 @@
export function clone (element) {
// clone element
let absolute = element.target.cloneNode(true)
let isOut = false

// give to the new element position absolute
absolute.style.position = 'absolute'
export function getPosition (target, center = true) {
// define absolute location of element
let bodyRectangular = document.body.getBoundingClientRect()
let elemRect = target.getBoundingClientRect()
let x = elemRect.left - bodyRectangular.left
let y = elemRect.top - bodyRectangular.top

// add the correct dimensions to the element
absolute.style.height = element.dimensions.height + 'px'
absolute.style.width = element.dimensions.width + 'px'
// if position should be center of element get center
if (center) {
// define dimensions of the element
let height = target.offsetHeight
let width = target.offsetWidth

// position element to exact spot
absolute.style.left = element.initialPosition.x - (element.dimensions.width / 2) + 'px'
absolute.style.top = element.initialPosition.y - (element.dimensions.height / 2) + 'px'
x += (width / 2)
y += (height / 2)
}

// append absolute element to the body
document.body.append(absolute)
return {x: x, y: y}
}

// hide old element but not remove it to keep the space
element.target.style.opacity = '0'
export function transitToInit (event, duration = 200) {
for (const element of follow.elements) {
transit(element.target)
}
}

// save element as before to add it back
element.before = element.target
export function transit (target, position = {x:0,y:0}, duration = 200) {
let transitionBefore = getComputedStyle(target).transition

// add new element as new target
element.target = absolute
// if duration is higher than 0 do transition
if (duration > 0) {
target.style.transition = duration + 'ms'

// reset transition after animation is done
setTimeout(() => {
target.style.transition = transitionBefore
}, duration + 10)
}

let transformAfter = ''

if (position.x !== 0 && position.y !== 0) {
// set transform to given position
transformAfter = `translate(${position.x}px, ${position.y}px)`
}
// set transform back to normal
target.style.transform = transformAfter
}

function transitToInitHelper (event) {
let from = event.relatedTarget || event.toElement
if (!from || from.nodeName === "HTML") {
transitToInit(200)
}
}

export function activateListeners () {
document.addEventListener('mousemove', follow.animate)
document.addEventListener('mouseout', transitToInitHelper)
}

export function destroyListeners () {
document.removeEventListener('mousemove', follow.animate)
document.removeEventListener('mouseout', transitToInitHelper)
}
119 changes: 33 additions & 86 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ window.follow = {
defaultFactor: 10,
init: undefined,
destroy: undefined,
animate: undefined
animate: undefined,
locked: false
}

follow.init = () => {
// get all targets with attribute
let targets = document.querySelectorAll(`[${follow.attribute}]`)

// log
debug.log('targets', targets)

for (let target of targets) {
Expand All @@ -27,132 +27,79 @@ follow.init = () => {

// create element object
let element = {
factor: factor,
target: target,
before: undefined,
x: 0,
y: 0,
dimensions: {
height: 0,
width: 0
factor: factor,
firstTransform: true,
position: {
x: 0,
y: 0
},
initialPosition: {
initial: {
x: 0,
y: 0
},
}

// define absolute dimensions of the element
let bodyRectangular = document.body.getBoundingClientRect()
let elemRect = element.target.getBoundingClientRect()
let offsetLeft = elemRect.left - bodyRectangular.left
let offsetTop = elemRect.top - bodyRectangular.top

// define dimensions of the element
element.dimensions.height = element.target.offsetHeight
element.dimensions.width = element.target.offsetWidth

// define the center of the element as initial position
element.initialPosition.x = offsetLeft + (element.dimensions.width / 2)
element.initialPosition.y = offsetTop + (element.dimensions.height / 2)

// if center helper is wanted
debug.dot(element.initialPosition.x, element.initialPosition.y, 'red', 10000)

// if position isn't absolute clone element
if (getComputedStyle(element.target).position !== 'absolute') {
helper.clone(element)
} else {
// if container has position relative, remove element and add back on top of all
let parent = element.target.parentElement

while (true) {
// if parent is body break out of while
if (parent.tagName === 'BODY') {
break
// else if position is relative
} else if (getComputedStyle(parent).position === 'relative') {
helper.clone(element)
break
}
parent = parent.parentElement
}
}
// define initial position
element.initial = helper.getPosition(element.target)

// add debug dot to the initial position of the target
debug.dot(element.initial.x, element.initial.y, 'red', 10000)

// push element to array
follow.elements.push(element)
}

// activate event listener
document.addEventListener('mousemove', follow.animate)
// activate event listeners
helper.activateListeners()

// log
debug.log('elements', follow.elements)
}

follow.destroy = () => {
// log
debug.log('follow.destroy called')

// remove the event listener
document.removeEventListener('mousemove', follow.animate)

for (const element of follow.elements) {
if (element.before) {
// if before element exists, bring it back and remove absolute element
element.before.style.opacity = '1'
element.target.remove()
} else {
// set the initial position for all elements
element.target.style.left = element.initialPosition.x - (element.dimensions.width / 2) + 'px'
element.target.style.top = element.initialPosition.y - (element.dimensions.height / 2) + 'px'
}
}
follow.destroy = (duration = 300) => {
// remove the event listeners
helper.destroyListeners()

// log
debug.log('follow.destroy() finished')
// transit elements to initial position
helper.transitToInit()

debug.log('follow destroyed')
}

follow.animate = (event) => {
// if the script is locked (mostly because of an animation) dont do animation
if (follow.locked) return

// define mouse position
let mouseX = event.clientX
let mouseY = event.clientY

// firefox fallback because mouse is offset by y 15 and x 9 pixel
if (navigator.userAgent.search("Firefox") !== -1) {
mouseX += 9
mouseY += 15
}

// add dot for mouse
// add debug dot to the mouse position
debug.dot(mouseX, mouseY, 'blue')

// log
debug.log('mouseX', mouseX)
debug.log('mouseY', mouseY)

for (let element of follow.elements) {
// log
debug.log('element', element)

// calculate additional pixels
let additionalX = (mouseX - element.initialPosition.x) / element.factor
let additionalY = (mouseY - element.initialPosition.y) / element.factor
let additionalX = (mouseX - element.initial.x) / element.factor
let additionalY = (mouseY - element.initial.y) / element.factor

// calculate future position
let futureX = (element.initialPosition.x + additionalX)
let futureY = (element.initialPosition.y + additionalY)
let futureX = (element.initial.x + additionalX)
let futureY = (element.initial.y + additionalY)

// add helper dot if wanted
debug.dot(futureX, futureY, 'green')

// set future position
element.target.style.left = futureX - (element.dimensions.width / 2) + 'px'
element.target.style.top = futureY - (element.dimensions.height / 2) + 'px'

// log
debug.log('future position x', futureX)
debug.log('future position y', futureY)

// set the additional pixels as css transform translate
element.target.style.transform = `translate(${additionalX}px, ${additionalY}px)`
}
}

Expand Down

0 comments on commit ca735d3

Please sign in to comment.