Compatibility: All Shopify themes.
Create an overlapping animated image text section in your website.
Was it possible to create an animation without applying it to all the sections in your website? Yes, most web developers will add an aos external library to do the work. Lets do this without a library though. Let our simple code do the work.
Check DEMO store here 💻. Password: made4uo
To start.
1. We have to add a section, so lets go to our code editor. In your shopify admin store, open themes, then click "Actions", then "Edit code".
2. Open the Sections folder, and add a new file. Lets name it "animated-image-text". Then paste the code below.
<animated-images> <div class="animatedContainer" id="animated--{{section.id}}" style="background: {{section.settings.section_bg}}; flex-direction: {{section.settings.placement}} "> <div class="imagesContainer"> {% assign firstImage = section.settings.img_1 %} {% assign secondImage = section.settings.img_2 %} {% if section.settings.slide_image %} <a {% if section.settings.img_1_link != blank %}href="{{section.settings.img_1_link }}" {% endif %} class="slideLink {% if section.settings.placement == 'row-reverse' %} slide--left {% else %} slide--right {% endif %}" style="width: 100%"> <img class="slideImage " style="width: 100%" alt="{{section.settings.img_1.alt }}" srcset="{% if firstImage.width >= 493 %}{{ firstImage| img_url: '493x' }} 493w,{% endif %} {% if firstImage.width >= 600 %}{{ firstImage | img_url: '600x' }} 600w,{% endif %} {% if firstImage.width >= 713 %}{{ firstImage| img_url: '713x' }} 713w,{% endif %} {% if firstImage.width >= 823 %}{{ firstImage| img_url: '823x' }} 823w,{% endif %} {% if firstImage.width >= 990 %}{{ firstImage| img_url: '990x' }} 990w,{% endif %} {% if firstImage.width >= 1100 %}{{ firstImage | img_url: '1100x' }} 1100w,{% endif %} {% if firstImage.width >= 1206 %}{{ firstImage | img_url: '1206x' }} 1206w,{% endif %} {% if firstImage.width >= 1346 %}{{ firstImage| img_url: '1346x' }} 1346w,{% endif %} {% if firstImage.width >= 1426 %}{{ firstImage| img_url: '1426x' }} 1426w,{% endif %} {% if firstImage.width >= 1646 %}{{ firstImage | img_url: '1646x' }} 1646w,{% endif %} {% if firstImage.width >= 1946 %}{{ firstImage| img_url: '1946x' }} 1946w,{% endif %} {{ firstImage | img_url: 'master' }} {{ firstImage }}w" src="{{ firstImage | img_url: '1946x' }}" loading="lazy" width="973"> </a> {% else %} {% if section.settings.img_1 != blank %} <a {% if section.settings.img_1_link != blank %}href="{{section.settings.img_1_link }}" {% endif %}class="firstLink" > <img class="firstImage" alt="{{section.settings.img_1.alt }}" srcset="{% if firstImage.width >= 493 %}{{ firstImage| img_url: '493x' }} 493w,{% endif %} {% if firstImage.width >= 600 %}{{ firstImage | img_url: '600x' }} 600w,{% endif %} {% if firstImage.width >= 713 %}{{ firstImage| img_url: '713x' }} 713w,{% endif %} {% if firstImage.width >= 823 %}{{ firstImage| img_url: '823x' }} 823w,{% endif %} {% if firstImage.width >= 990 %}{{ firstImage| img_url: '990x' }} 990w,{% endif %} {% if firstImage.width >= 1100 %}{{ firstImage | img_url: '1100x' }} 1100w,{% endif %} {% if firstImage.width >= 1206 %}{{ firstImage | img_url: '1206x' }} 1206w,{% endif %} {% if firstImage.width >= 1346 %}{{ firstImage| img_url: '1346x' }} 1346w,{% endif %} {% if firstImage.width >= 1426 %}{{ firstImage| img_url: '1426x' }} 1426w,{% endif %} {% if firstImage.width >= 1646 %}{{ firstImage | img_url: '1646x' }} 1646w,{% endif %} {% if firstImage.width >= 1946 %}{{ firstImage| img_url: '1946x' }} 1946w,{% endif %} {{ firstImage | img_url: 'master' }} {{ firstImage }}w" src="{{ firstImage | img_url: '1946x' }}" loading="lazy" width="973"> </a> {% endif %} {% if section.settings.img_2 != blank %} <a {% if section.settings.img_2_link != blank %}href="{{section.settings.img_2_link }}" {% endif %}class="secondLink" > <img class="secondImage" alt="{{section.settings.img_2.alt }}" srcset="{% if firstImage.width >= 493 %}{{ firstImage| img_url: '493x' }} 493w,{% endif %} {% if secondImage.width >= 600 %}{{ secondImage | img_url: '600x' }} 600w,{% endif %} {% if secondImage.width >= 713 %}{{ secondImage| img_url: '713x' }} 713w,{% endif %} {% if secondImage.width >= 823 %}{{ secondImage | img_url: '823x' }} 823w,{% endif %} {% if secondImage.width >= 990 %}{{ secondImage| img_url: '990x' }} 990w,{% endif %} {% if secondImage.width >= 1100 %}{{ secondImage | img_url: '1100x' }} 1100w,{% endif %} {% if secondImage.width >= 1206 %}{{ secondImage | img_url: '1206x' }} 1206w,{% endif %} {% if secondImage.width >= 1346 %}{{ secondImage| img_url: '1346x' }} 1346w,{% endif %} {% if secondImage.width >= 1426 %}{{ secondImage| img_url: '1426x' }} 1426w,{% endif %} {% if secondImage.width >= 1646 %}{{ secondImage | img_url: '1646x' }} 1646w,{% endif %} {% if secondImage.width >= 1946 %}{{ secondImage| img_url: '1946x' }} 1946w,{% endif %} {{ secondImage | img_url: 'master' }} {{ secondImage }}w" src="{{ secondImage | img_url: '1946x' }}" loading="lazy" width="973"> </a> {% endif %} {% endif %} </div> <div class="textContainer" style="color: {{section.settings.section_text}};"> {% if section.settings.title != blank %} <h1 style="color: {{section.settings.section_text}};"> {{ section.settings.title }}</h1>{% endif %} {{ section.settings.description }} {% if section.settings.button_link and section.settings.button != blank %} <a href="{{ section.settings.button_link }}" class="{{section.settings.button_style}}"> {{ section.settings.button}}</a> {% endif %} </div> </div> </animated-images> <style> .animatedContainer { width: 100%; padding: 5% 10%; display: flex; gap: 40px; position: relative; justify-content: space-between; align-items: center; background: {{section.settings.section_bg}}; } .imagesContainer { display: flex; align-items: center; flex: 0 1 50%; margin-left: -15px; margin-bottom: 5rem } .imagesContainer > a > img { width: 100%; } .imagesContainer .slideLink { z-index: 1; } .imagesContainer .firstLink { z-index: 1; flex: 0 1 50%; transform: translate(50px, 50px); } .imagesContainer .secondLink { flex: 0 1 50%; z-index: 1; } .textContainer { width: 50%; display: flex; justify-content: center; flex-direction: column; min-width: 43%; flex: 0 1 43%; align-items: center; } .textContainer h1 { font-style: normal; font-weight: 700; margin: 0; } .textContainer p { text-align: center; } .textContainer a { text-decoration: none; padding: 10px 20px; border: 2px solid {{section.settings.section_text}}; display: inline-block; width: max-content; border-radius: 0rem; } .button--primary { color: white; background: {{section.settings.section_text}}; } .button--secondary { color: {{section.settings.section_text}}; } .slide--left { flex-direction: row-reverse; } .slide--right { flex-direction: row; } .section-animation .slide--left { transition: margin-left 1s cubic-bezier(.215,.61,.355,1); animation: slideFromLeft 1s cubic-bezier(.215,.61,.355,1); } .section-animation .slide--right { transition: margin-left 1s cubic-bezier(.215,.61,.355,1); animation: slideFromRight 1s cubic-bezier(.215,.61,.355,1); } .section-animation .firstLink{ transition: opacity .5s ease, width 1s ease, transform .5s cubic-bezier(.2, .06, .05, .95); animation: fade-in .6s ease, first-image .6s cubic-bezier(.2, .06, .05, .95); } .section-animation .secondLink { transition: opacity .5s ease, width 1s ease, transform .5s cubic-bezier(.2, .06, .05, .95); animation: fade-in .6s ease , second-image .6s cubic-bezier(.2, .06, .05, .95); } .section-animation .textContainer { transition: opacity .9s cubic-bezier(.04, 0, .2, 1),transform .9s cubic-bezier(.04, 0, .2, 1); animation: move-up .8s ease; } @keyframes slideFromLeft { 0% { opacity: 0; margin-left: 5%; } 100%{ opacity: 1; margin-left: 0; } } @keyframes slideFromRight{ 0% { opacity: 0; margin-left: -5%; } 100%{ opacity: 1; margin-left: 0; } } @keyframes first-image { 0% { transform: translate(-50px, 50px); } 100%{ transform: translate(50px, 50px); } } @keyframes second-image { 0% { transform: translateX(50px); } 100%{ transform: translate(0); } } @keyframes fade-in { 0% { opacity: 0; width: 110%; } 100%{ opacity: 1; width: 100%; } } @keyframes move-up { 0% { opacity: 0; transform: translateY(100px); } 100%{ opacity: 1; transform: translateY(0px); } } @media only screen and (max-width: 900px) { .animatedContainer { flex-direction: column !important; padding: 5% 8%; } .imagesContainer { width: 100%; } .textContainer { width: 100%; padding: 0 5%; } } </style> {% schema %} { "name": "Animated section", "settings": [ { "type": "select", "id": "placement", "default": "row", "label": "Placement of the items", "options": [ { "value": "row-reverse", "label": "Image to right" }, { "value": "row", "label": "Image to left" } ] }, { "type": "image_picker", "id": "img_1", "label": "First image" }, { "type": "url", "id": "img_1_link", "label": "First image link" }, { "type": "image_picker", "id": "img_2", "label": "Second image" }, { "type": "url", "id": "img_2_link", "label": "Second image link" }, { "type": "text", "id": "title", "label": "Title" }, { "type": "richtext", "id": "description", "label": "Description" }, { "type": "color", "id": "section_bg", "default": "#FFFFFF", "label": "Section background" }, { "type": "color", "id": "section_text", "default": "#000", "label": "Section text color" }, { "type": "url", "id": "button_link", "label": "Button link" }, { "type": "text", "id": "button", "label": "Button label" }, { "type": "select", "id": "button_style", "default": "button--primary", "label": "Button style", "options": [ { "value": "button--primary", "label": "Primary button" }, { "value": "button--secondary", "label": "Secondary button" } ] } ], "presets": [ { "name": "Animated Section" } ] } {% endschema %} |
3. Since we are listening to the user's scrolls, lets add a javascript code. There is a way to do it with CSS but not all browsers were supporting it. Anyway, in the Asset folder, open the global.js.
class AnimatedBox extends HTMLElement { constructor() { super(); this.animated = this.querySelector('.animatedContainer'); this.animatedItems = this.querySelectorAll('.animatedContainer'); this.createObserver(); } createObserver() { const revealSection = function(entries, observer) { const [entry] = entries; if(!entry.isIntersecting || entry.boundingClientRect.top < 0) { entry.target.classList.remove('section-animation'); } else { entry.target.classList.add('section-animation'); } }; let sectionObserver = new IntersectionObserver( revealSection); this.animatedItems.forEach(function(section) { sectionObserver.observe(section); section.classList.remove('section-animation'); }) } } customElements.define('animated-images', AnimatedBox); |
That's it.
Note: I would suggest placing it as a third section or greater, so javascript have time to scroll and call the animation