Attempt to re-create CJ Gammon’s portfolio grid hover effect using SVG clip-path and CSS Transitions.
A Pen by Noel Delgado on CodePen.
| :ruby | |
| @items = [ | |
| { :t => 'X-rays', :i => 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/9473/i-xrays.png' }, | |
| { :t => 'Worms', :i => 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/9473/i-worms.png' }, | |
| { :t => 'Aurora', :i => 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/9473/i-aurora.png' }, | |
| { :t => 'Angus', :i => 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/9473/i-angus.png' }, | |
| { :t => 'Huitzi', :i => 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/9473/i-huitzi.png' }, | |
| { :t => 'Dalí', :i => 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/9473/i-dali.png' }, | |
| { :t => 'The Bride', :i => 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/9473/i-bride.png' }, | |
| { :t => 'The Man', :i => 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/9473/i-man.png' }, | |
| { :t => 'D', :i => 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/9473/i-d.png' }, | |
| { :t => 'V', :i => 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/9473/i-v.png' }, | |
| { :t => 'V II', :i => 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/9473/i-v2.png' }, | |
| { :t => 'V III', :i => 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/9473/i-v3.png' } | |
| ] | |
| .container | |
| %header | |
| %h1 SVG clip-path Hover Effect | |
| %p | |
| Attempt to re-create <a href="http://www.cjgammon.com/" target="_blank">CJ Gammon’s</a> | |
| portfolio grid hover effect using SVG and CSS Transitions. | |
| %p.small | |
| <b>Note:</b> this is an experiment, it does not seem to work on Firefox 43.0.4 | |
| neither have touch support. | |
| <br/>Tested on Chrome 47.0.2526.106, Opera 34.0 and Safari 8.0.6. | |
| %main | |
| .items | |
| - @items.each_with_index do | item, index | | |
| .item | |
| %svg{:viewBox => "0 0 300 200", :preserveAspectRatio => "xMidYMid slice"} | |
| %defs | |
| %clipPath{:id => "clip-#{index}"} | |
| %circle{:cx => "0", :cy => "0", :r => "150px", :fill => "#000"} | |
| %text{:class => "svg-text", :x => "50%", :y => "50%", :dy => ".3em"} | |
| #{item[:t]} | |
| %g{:"clip-path" => "url(#clip-#{index})"} | |
| %image{"xlink:href" => "#{item[:i]}", :width => "100%", :height => "100%", :preserveAspectRatio => "xMinYMin slice"} | |
| %text{:class => "svg-masked-text", :x => "50%", :y => "50%", :dy => ".3em"} | |
| #{item[:t]} | |
| .options | |
| %button.dark | |
| %button.light |
| /* | |
| * Noel Delgado | @pixelia_me | |
| */ | |
| var items = [] | |
| , point = document.querySelector('svg').createSVGPoint(); | |
| function getCoordinates(e, svg) { | |
| point.x = e.clientX; | |
| point.y = e.clientY; | |
| return point.matrixTransform(svg.getScreenCTM().inverse()); | |
| } | |
| function changeColor(e) { | |
| document.body.className = e.currentTarget.className; | |
| } | |
| function Item(config) { | |
| Object.keys(config).forEach(function (item) { | |
| this[item] = config[item]; | |
| }, this); | |
| this.el.addEventListener('mousemove', this.mouseMoveHandler.bind(this)); | |
| this.el.addEventListener('touchmove', this.touchMoveHandler.bind(this)); | |
| } | |
| Item.prototype = { | |
| update: function update(c) { | |
| this.clip.setAttribute('cx', c.x); | |
| this.clip.setAttribute('cy', c.y); | |
| }, | |
| mouseMoveHandler: function mouseMoveHandler(e) { | |
| this.update(getCoordinates(e, this.svg)); | |
| }, | |
| touchMoveHandler: function touchMoveHandler(e) { | |
| e.preventDefault(); | |
| var touch = e.targetTouches[0]; | |
| if (touch) return this.update(getCoordinates(touch, this.svg)); | |
| } | |
| }; | |
| [].slice.call(document.querySelectorAll('.item'), 0).forEach(function (item, index) { | |
| items.push(new Item({ | |
| el: item, | |
| svg: item.querySelector('svg'), | |
| clip: document.querySelector('#clip-'+index+' circle'), | |
| })); | |
| }); | |
| [].slice.call(document.querySelectorAll('button'), 0).forEach(function (button) { | |
| button.addEventListener('click', changeColor); | |
| }); |
| $db: #2f3238; | |
| $dc: #f5f5f5; | |
| $lb: #f9f9f9; | |
| $lc: #1a1a1a; | |
| $l: #1abc89; | |
| * {margin: 0; box-sizing: border-box;} | |
| :root { | |
| font-size: 13px; | |
| font-family: 'Source Sans Pro', sans-serif; | |
| line-height: 1.618; | |
| font-weight: 400; | |
| } | |
| body { | |
| background-color: $db; | |
| color: $dc; | |
| } | |
| a {color: $l} | |
| a:hover {opacity: .8} | |
| p { | |
| font-size: 1.2rem; | |
| color: rgba($dc, .5); | |
| } | |
| .small { | |
| font-size: 1rem; | |
| margin-top: 1em; | |
| } | |
| .container { | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| padding: 4rem 2rem; | |
| } | |
| header { | |
| text-align: center; | |
| padding-bottom: 3rem; | |
| } | |
| h1 { | |
| font-size: 2.6rem; | |
| line-height: 1.2em; | |
| padding-bottom: 1rem; | |
| font-weight: 600; | |
| } | |
| svg { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| } | |
| circle { | |
| transform-origin: 50% 50%; | |
| transform: scale(0); | |
| transition: transform 200ms cubic-bezier(0.250, 0.460, 0.450, 0.940); | |
| } | |
| text { | |
| font-size: 1.1rem; | |
| text-transform: uppercase; | |
| text-anchor: middle; | |
| letter-spacing: 1px; | |
| font-weight: 600; | |
| } | |
| .svg-text {fill: lighten($db, 16)} | |
| .svg-masked-text {fill: rgba(#fff, 1);} | |
| image { | |
| transform: scale(1.1); | |
| transform-origin: 50% 50%; | |
| transition: transform 200ms cubic-bezier(0.250, 0.460, 0.450, 0.940); | |
| } | |
| .items { | |
| display: flex; | |
| flex-flow: row wrap; | |
| justify-content: center; | |
| } | |
| .item { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| position: relative; | |
| overflow: hidden; | |
| width: 300px; | |
| height: 200px; | |
| margin: 5px; | |
| cursor: pointer; | |
| background-color: lighten($db, 5); | |
| border-radius: 2px; | |
| box-shadow: 0 5px 5px rgba(0, 0, 0, 0.02), inset 0 0px 0px 1px rgba(0, 0, 0, 0.07); | |
| transform: translateZ(0); | |
| } | |
| .item:hover { | |
| circle, | |
| image {transform: scale(1)} | |
| } | |
| button { | |
| width: 12px; | |
| height: 12px; | |
| border: none; | |
| appearance: none; | |
| box-shadow: 0 0 0 1px rgba(0,0,0,.5); | |
| border-radius: 1px; | |
| &.dark {background-color: $db;} | |
| &.light {background-color: $lb;} | |
| } | |
| .options { | |
| position: absolute; | |
| top: 1rem; | |
| right: 1rem; | |
| button {margin-left: .5rem} | |
| } | |
| .light { | |
| background-color: $lb; | |
| color: $lc; | |
| p {color: rgba($lc, .5);} | |
| .item {background: $dc;} | |
| .svg-text {fill: rgba(#000, .1);} | |
| } |
Attempt to re-create CJ Gammon’s portfolio grid hover effect using SVG clip-path and CSS Transitions.
A Pen by Noel Delgado on CodePen.