├── .DS_Store ├── .gitattributes ├── README.md ├── admin ├── .DS_Store ├── base.gif └── base.png ├── css └── base.css ├── img ├── .DS_Store └── 1.jpg ├── index.html └── js └── index.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python019/motion-effect-svg-filter-gsap/1ac4c4c3c19c5e18f84f43b7a0de6d327c6f6ede/.DS_Store -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.md linguist-language=scss 2 | *.html linguist-language=js 3 | *.css linguist-language=js -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # Image Motion Effect with SVG Filter Gsap | Crimson 4 | 5 | 6 | 7 | ### by SUBUX 8 | 9 |
-------------------------------------------------------------------------------- /admin/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python019/motion-effect-svg-filter-gsap/1ac4c4c3c19c5e18f84f43b7a0de6d327c6f6ede/admin/.DS_Store -------------------------------------------------------------------------------- /admin/base.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python019/motion-effect-svg-filter-gsap/1ac4c4c3c19c5e18f84f43b7a0de6d327c6f6ede/admin/base.gif -------------------------------------------------------------------------------- /admin/base.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python019/motion-effect-svg-filter-gsap/1ac4c4c3c19c5e18f84f43b7a0de6d327c6f6ede/admin/base.png -------------------------------------------------------------------------------- /css/base.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::after, 3 | *::before { 4 | box-sizing: border-box; 5 | } 6 | 7 | :root { 8 | font-size: 15px; 9 | } 10 | 11 | body { 12 | margin: 0; 13 | --color-text: #fff; 14 | --color-bg: #0d0d0e; 15 | --color-link: #b3382c; 16 | --color-link-hover: #d6665b; 17 | color: var(--color-text); 18 | background: var(--color-bg); 19 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif; 20 | font-weight: 500; 21 | -webkit-font-smoothing: antialiased; 22 | -moz-osx-font-smoothing: grayscale; 23 | width: 100%; 24 | height: 100vh; 25 | overflow: hidden; 26 | } 27 | 28 | a { 29 | text-decoration: none; 30 | color: var(--color-link); 31 | outline: none; 32 | } 33 | 34 | a:hover { 35 | color: var(--color-link-hover); 36 | outline: none; 37 | } 38 | 39 | a:focus { 40 | outline: none; 41 | background: lightgrey; 42 | } 43 | 44 | a:focus:not(:focus-visible) { 45 | background: transparent; 46 | } 47 | 48 | a:focus-visible { 49 | outline: 2px solid red; 50 | background: transparent; 51 | } 52 | 53 | .frame { 54 | padding: 1.5rem 2rem 10vh; 55 | text-align: center; 56 | position: relative; 57 | z-index: 100; 58 | } 59 | 60 | .frame__title { 61 | margin: 0; 62 | font-size: 1rem; 63 | font-weight: 500; 64 | } 65 | 66 | .frame__links { 67 | margin: 0.5rem 0 2rem; 68 | } 69 | 70 | .frame__links a:not(:last-child) { 71 | margin-right: 1rem; 72 | } 73 | 74 | .content { 75 | flex: 1; 76 | display: grid; 77 | place-items: center; 78 | position: absolute; 79 | width: 100%; 80 | height: 100%; 81 | } 82 | 83 | #theSVG { 84 | position: relative; 85 | display: grid; 86 | place-items: center; 87 | max-height: 65vh; 88 | grid-area: 1 / 1 / 2 / 2; 89 | will-change: transform; 90 | } 91 | 92 | @media screen and (min-width: 53em) { 93 | main { 94 | height: 100vh; 95 | display: flex; 96 | flex-direction: column; 97 | } 98 | .frame { 99 | padding: 1.5rem 2rem 0; 100 | display: grid; 101 | grid-template-columns: auto 1fr auto; 102 | grid-template-areas: 'title links sponsor'; 103 | grid-gap: 3vw; 104 | justify-content: space-between; 105 | text-align: left; 106 | } 107 | .frame__links { 108 | margin: 0; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /img/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python019/motion-effect-svg-filter-gsap/1ac4c4c3c19c5e18f84f43b7a0de6d327c6f6ede/img/.DS_Store -------------------------------------------------------------------------------- /img/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python019/motion-effect-svg-filter-gsap/1ac4c4c3c19c5e18f84f43b7a0de6d327c6f6ede/img/1.jpg -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Image Motion Effect with SVG Filter Gsap| Crimson 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /js/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Linear interpolation 3 | * @param {Number} a - first value to interpolate 4 | * @param {Number} b - second value to interpolate 5 | * @param {Number} n - amount to interpolate 6 | */ 7 | const lerp = (a, b, n) => (1 - n) * a + n * b; 8 | 9 | /** 10 | * Gets the cursor position 11 | * @param {Event} ev - mousemove event 12 | */ 13 | const getCursorPos = ev => { 14 | return { 15 | x : ev.clientX, 16 | y : ev.clientY 17 | }; 18 | }; 19 | 20 | /** 21 | * Map number x from range [a, b] to [c, d] 22 | * @param {Number} x - changing value 23 | * @param {Number} a 24 | * @param {Number} b 25 | * @param {Number} c 26 | * @param {Number} d 27 | */ 28 | const map = (x, a, b, c, d) => (x - a) * (d - c) / (b - a) + c; 29 | 30 | /** 31 | * Distance between point A(x1,y1) and B(x2,y2) 32 | * @param {Number} x1 33 | * @param {Number} x2 34 | * @param {Number} y1 35 | * @param {Number} y2 36 | */ 37 | const distance = (x1,x2,y1,y2) => { 38 | var a = x1 - x2; 39 | var b = y1 - y2; 40 | return Math.hypot(a,b); 41 | }; 42 | 43 | /** 44 | * Calculates the viewport size 45 | */ 46 | const calcWinsize = () => { 47 | return { 48 | width: window.innerWidth, 49 | height: window.innerHeight 50 | } 51 | } 52 | 53 | // Viewport size 54 | let winsize = calcWinsize(); 55 | // Re-calculate on resize 56 | window.addEventListener('resize', () => winsize = calcWinsize()); 57 | 58 | // Track the cursor position 59 | let cursor = {x: winsize.width/2, y: winsize.height/2}; 60 | let cachedCursor = cursor; 61 | window.addEventListener('mousemove', ev => cursor = getCursorPos(ev)); 62 | 63 | /** 64 | * Class representing an SVG image that follows the cursor 65 | * and gets "distorted" as the cursor moves faster. 66 | */ 67 | class SVGImageFilterEffect { 68 | // DOM elements 69 | DOM = { 70 | // Main element (SVG element) 71 | el: null, 72 | // the SVG image 73 | image: null, 74 | // feDisplacementMap element 75 | feDisplacementMapEl: null, 76 | }; 77 | // option defaults 78 | defaults = { 79 | // How much to translate and rotate the image 80 | // Also the range of the displacementScale values 81 | valuesFromTo: { 82 | transform: { 83 | x: [-120,120], 84 | y: [-120,120], 85 | rz: [-10,10] 86 | }, 87 | displacementScale: [0, 400], 88 | }, 89 | // The "amt" is the amount to interpolate. 90 | // With interpolation, we can achieve a smooth animation effect when moving the cursor. 91 | amt: { 92 | transform: 0.1, 93 | displacementScale: 0.06, 94 | }, 95 | }; 96 | // Values that change when moving the cursor (transform and feDisplacementMap scale values) 97 | imgValues = { 98 | imgTransforms: {x: 0, y: 0, rz: 0}, 99 | displacementScale: 0, 100 | }; 101 | 102 | /** 103 | * Constructor. 104 | * @param {Element} DOM_el - the SVG element 105 | * @param {JSON} options 106 | */ 107 | constructor(DOM_el, options) { 108 | this.DOM.el = DOM_el; 109 | this.DOM.image = this.DOM.el.querySelector('image'); 110 | this.DOM.feDisplacementMapEl = this.DOM.el.querySelector('feDisplacementMap'); 111 | 112 | this.options = Object.assign(this.defaults, options); 113 | 114 | requestAnimationFrame(() => this.render()); 115 | } 116 | /** 117 | * Loop / Interpolation 118 | */ 119 | render() { 120 | // Apply interpolated values (smooth effect) 121 | this.imgValues.imgTransforms.x = lerp(this.imgValues.imgTransforms.x, map(cursor.x, 0, winsize.width, this.options.valuesFromTo.transform.x[0], this.options.valuesFromTo.transform.x[1]), this.options.amt.transform); 122 | this.imgValues.imgTransforms.y = lerp(this.imgValues.imgTransforms.y, map(cursor.y, 0, winsize.height, this.options.valuesFromTo.transform.y[0], this.options.valuesFromTo.transform.y[1]), this.options.amt.transform); 123 | this.imgValues.imgTransforms.rz = lerp(this.imgValues.imgTransforms.rz, map(cursor.x, 0, winsize.width, this.options.valuesFromTo.transform.rz[0], this.options.valuesFromTo.transform.rz[1]), this.options.amt.transform); 124 | 125 | this.DOM.el.style.transform = `translateX(${(this.imgValues.imgTransforms.x)}px) translateY(${this.imgValues.imgTransforms.y}px) rotateZ(${this.imgValues.imgTransforms.rz}deg)`; 126 | 127 | const cursorTravelledDistance = distance(cachedCursor.x, cursor.x, cachedCursor.y, cursor.y); 128 | this.imgValues.displacementScale = lerp(this.imgValues.displacementScale, map(cursorTravelledDistance, 0, 200, this.options.valuesFromTo.displacementScale[0], this.options.valuesFromTo.displacementScale[1]), this.options.amt.displacementScale); 129 | this.DOM.feDisplacementMapEl.scale.baseVal = this.imgValues.displacementScale; 130 | 131 | cachedCursor = cursor; 132 | 133 | // loop... 134 | requestAnimationFrame(() => this.render()); 135 | } 136 | } 137 | 138 | // Initialize trail effect 139 | new SVGImageFilterEffect(document.querySelector('#theSVG')); --------------------------------------------------------------------------------