Last active
May 28, 2019 17:14
-
-
Save tenthree/1e1785b1bc0566103241076fbffb15c2 to your computer and use it in GitHub Desktop.
Vue.js PhotoCompared component
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<div class="photo-compared" :class="className" @mousedown="onPointDown" @touchstart="onPointDown"> | |
<div class="photo-compared__frame" :style="frameStyle"></div> | |
<div class="photo-compared__before" :style="beforeStyle"></div> | |
<div class="photo-compared__after" :style="afterStyle"></div> | |
<div class="photo-compared__handler" :style="handlerStyle"></div> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: 'PhotoCompared', | |
props: { | |
before: String, | |
after: String, | |
width: { type: Number, default: 16 }, | |
height: { type: Number, default: 9 }, | |
value: { type: Number, default: 0.5 }, | |
vertical: { type: Boolean, default: false } | |
}, | |
data () { | |
return { | |
rect: { x: 0, y: 0, width: 0, height: 0 }, | |
firstTouchMovedPosition: null, | |
position: { x: 0, y: 0 }, | |
percent: { x: 0, y: 0 } | |
} | |
}, | |
computed: { | |
className () { | |
return { | |
'photo-compared--vertical': this.vertical | |
} | |
}, | |
frameStyle () { | |
let ratio = Math.round(this.height / this.width * 10000) / 100 | |
return { | |
'padding-bottom': `${ratio}%` | |
} | |
}, | |
beforeStyle () { | |
return { | |
'background-image': `url(${this.before})` | |
} | |
}, | |
afterStyle () { | |
let right, bottom | |
if (!this.vertical) { | |
right = Math.round(this.percent.x * this.rect.width) | |
bottom = this.rect.height | |
} else { | |
right = this.rect.width | |
bottom = Math.round(this.percent.y * this.rect.height) | |
} | |
return { | |
'background-image': `url(${this.after})`, | |
clip: `rect(0px, ${right}px, ${bottom}px, 0px)` | |
} | |
}, | |
handlerStyle () { | |
let direction = !this.vertical ? 'x' : 'y' | |
let percent = Math.round(this.percent[direction] * 10000) / 100 | |
let translate = !this.vertical ? 'translateX' : 'translateY' | |
return { | |
transform: `${translate}(${percent}%)` | |
} | |
} | |
}, | |
methods: { | |
getPositionFromViewport (el) { | |
let pos = { x: el.offsetLeft, y: el.offsetTop } | |
let parent = el.offsetParent | |
while (parent) { | |
pos.x += parent.offsetLeft | |
pos.y += parent.offsetTop | |
parent = parent.offsetParent | |
} | |
pos.x -= window.pageXOffset | |
pos.y -= window.pageYOffset | |
return { ...pos } | |
}, | |
onResize () { | |
let el = this.$el | |
let origin = this.getPositionFromViewport(el) | |
this.rect = { | |
x: origin.x, | |
y: origin.y, | |
width: el.offsetWidth, | |
height: el.offsetHeight | |
} | |
}, | |
onPointDown (event) { | |
let isTouch = event.type.indexOf('touch') === 0 | |
let point = isTouch ? event.touches[0] : event | |
if (!point) { | |
return | |
} | |
let { clientX, clientY } = point | |
// console.log('[begin]', clientX, clientY) | |
this.updatePosition(clientX, clientY) | |
if (!isTouch) { | |
// mouse | |
window.addEventListener('mousemove', this.onPointMove) | |
window.addEventListener('mouseup', this.onPointUp) | |
} else { | |
// touch | |
window.addEventListener('touchmove', this.onPointMove, { passive: false }) | |
window.addEventListener('touchend', this.onPointUp) | |
window.addEventListener('touchcancel', this.onPointUp) | |
} | |
}, | |
onPointMove (event) { | |
event.preventDefault() | |
let isTouch = event.type.indexOf('touch') === 0 | |
let point = isTouch ? event.touches[0] : event | |
if (!point) { | |
return | |
} | |
let { clientX, clientY } = point | |
// console.log('[move]', clientX, clientY) | |
this.updatePosition(clientX, clientY) | |
}, | |
onPointUp (event) { | |
let isTouch = event.type.indexOf('touch') === 0 | |
let point = isTouch ? event.touches[0] : event | |
if (!isTouch) { | |
// mouse | |
window.removeEventListener('mousemove', this.onPointMove) | |
window.removeEventListener('mouseup', this.onPointUp) | |
} else { | |
// touch | |
window.removeEventListener('touchmove', this.onPointMove) | |
window.removeEventListener('touchend', this.onPointUp) | |
window.removeEventListener('touchcancel', this.onPointUp) | |
} | |
if (!point) { | |
return | |
} | |
let { clientX, clientY } = point | |
// console.log('[end]', clientX, clientY) | |
this.updatePosition(clientX, clientY) | |
}, | |
updatePosition (x = null, y = null) { | |
let rect = this.rect | |
if (x !== null) { | |
x = x - rect.x | |
if (x < 0) { | |
x = 0 | |
} else if (x > rect.width) { | |
x = rect.width | |
} | |
this.position.x = x | |
this.percent.x = x / rect.width | |
} | |
if (y !== null) { | |
y = y - rect.y | |
if (y < 0) { | |
y = 0 | |
} else if (y > rect.height) { | |
y = rect.height | |
} | |
this.position.y = y | |
this.percent.y = y / rect.height | |
} | |
}, | |
updatePercent (x = null, y = null) { | |
let rect = this.rect | |
if (x != null) { | |
if (x < 0) { | |
x = 0 | |
} else if (x > 1) { | |
x = 1 | |
} | |
this.percent.x = x | |
this.position.x = x * rect.width | |
} | |
if (y != null) { | |
if (y < 0) { | |
y = 0 | |
} else if (y > 1) { | |
y = 1 | |
} | |
this.percent.y = y | |
this.position.y = y * rect.height | |
} | |
} | |
}, | |
beforeDestroy () { | |
window.removeEventListener('resize', this.onResize) | |
}, | |
mounted () { | |
window.addEventListener('resize', this.onResize) | |
this.onResize() | |
if (!this.vertical) { | |
this.updatePercent(this.value, null) | |
} else { | |
this.updatePercent(null, this.value) | |
} | |
} | |
} | |
</script> | |
<style lang="scss"> | |
.photo-compared { | |
position: relative; | |
width: auto; | |
overflow: hidden; | |
user-select: none; | |
cursor: move; | |
&__frame { | |
position: relative; | |
width: 100%; | |
height: 0; | |
padding-bottom: 56.25%; | |
} | |
&__before, | |
&__after { | |
position: absolute; | |
top: 0; | |
bottom: 0; | |
left: 0; | |
right: 0; | |
background: no-repeat center center/cover; | |
backface-visibility: hidden; | |
transform-origin: left top; | |
} | |
&__after { | |
// transition: clip 0.2s ease-out; | |
} | |
&__handler { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
margin: 0 0 0 -2px; | |
border-left: 4px solid white; | |
// transition: transform 0.2s ease-out; | |
box-shadow: 0 4px 8px -6px black; | |
} | |
$comp: &; | |
&--vertical { | |
#{$comp}__handler { | |
margin: -2px 0 0 0; | |
border-left: none; | |
border-top: 4px solid white; | |
} | |
} | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment