+2

Tạo form login độc đáo bằng react

Bài viết được lấy ý tưởng từ một example trên codepen: https://codepen.io/dsenneff/full/2c3e5bc86b372d5424b00edaf4990173

Example sử dụng thư viện gsap của javasxript kết hợp với svg để làm hình động khi mình input vào form nhìn rất cute.

Nhưng được viết bằng html và js thuần, nên mình sẽ chuyển nó sang react cho vui, có thể sau này sẽ nhúng vào dự án react nào đó thì sẽ rất cool ngầu 😃)

Khởi tạo ứng dụng react

Chúng ta sẽ sử dụng react-create-app để khởi tạo ứng dụng, trong bài viết mình sử dụng yarn, bạn cũng có thể sử dụng npm để thay thế nếu muốn

yarn global add create-react-app

Khởi tạo project, tên project mình khởi tạo là login-svg-react

create-react-app login-svg-react

Sau khi cài đặt xong, ta di chuyển vào thư mục project cd login-svg-react Vào được thư mục project rồi thì khởi chạy ứng dung và code thôi

yarn start

Sau khi chạy trình duyệt sẽ mở ra một trang, mặc định là localhost:3000

Thư viện cần dùng

Example đang sử dụng các thư viện bằng cách nhúng thẳng vào trang web thông qua các thẻ csript. Khi chuyển sang react ta sẽ dùng thư viện đó thông qua trình quản lý gói của javascriptnpm nhé.

Các thư viện cần thêm

  • gsap
  • eases
  • node-sass

Cài đặt các thư viện cần thiết

yarn add gsap eases node-sass

Chuyển HTML thuần sang react

Các bạn ở file src/App.js và thay thế bằng nội dung này nhé

import React, { Component } from 'react';
import { TweenMax, Power2, Expo } from "gsap/TweenMax";
import Quad  from "eases";
import './App.scss';

class App extends Component {

 componentDidMount() {

   var emailLabel = document.querySelector('#loginEmailLabel'), email = document.querySelector('#loginEmail'), passwordLabel = document.querySelector('#loginPasswordLabel'), password = document.querySelector('#loginPassword'), showPasswordCheck = document.querySelector('#showPasswordCheck'), showPasswordToggle = document.querySelector('#showPasswordToggle'), mySVG = document.querySelector('.svgContainer'), twoFingers = document.querySelector('.twoFingers'), armL = document.querySelector('.armL'), armR = document.querySelector('.armR'), eyeL = document.querySelector('.eyeL'), eyeR = document.querySelector('.eyeR'), nose = document.querySelector('.nose'), mouth = document.querySelector('.mouth'), mouthBG = document.querySelector('.mouthBG'), mouthSmallBG = document.querySelector('.mouthSmallBG'), mouthMediumBG = document.querySelector('.mouthMediumBG'), mouthLargeBG = document.querySelector('.mouthLargeBG'), mouthMaskPath = document.querySelector('#mouthMaskPath'), mouthOutline = document.querySelector('.mouthOutline'), tooth = document.querySelector('.tooth'), tongue = document.querySelector('.tongue'), chin = document.querySelector('.chin'), face = document.querySelector('.face'), eyebrow = document.querySelector('.eyebrow'), outerEarL = document.querySelector('.earL .outerEar'), outerEarR = document.querySelector('.earR .outerEar'), earHairL = document.querySelector('.earL .earHair'), earHairR = document.querySelector('.earR .earHair'), hair = document.querySelector('.hair'), bodyBG = document.querySelector('.bodyBGnormal'), bodyBGchanged = document.querySelector('.bodyBGchanged');
   var activeElement, curEmailIndex, screenCenter, svgCoords, emailCoords, emailScrollMax, chinMin = .5, dFromC, mouthStatus = "small", blinking, eyeScale = 1, eyesCovered = false, showPasswordClicked = false;
   var eyeLCoords, eyeRCoords, noseCoords, mouthCoords, eyeLAngle, eyeLX, eyeLY, eyeRAngle, eyeRX, eyeRY, noseAngle, noseX, noseY, mouthAngle, mouthX, mouthY, mouthR, chinX, chinY, chinS, faceX, faceY, faceSkew, eyebrowSkew, outerEarX, outerEarY, hairX, hairS;

   function calculateFaceMove(e) {
     var
       carPos = email.selectionEnd,
       div = document.createElement('div'),
       span = document.createElement('span'),
       copyStyle = getComputedStyle(email),
       caretCoords = {}
       ;
     if (carPos === null || carPos === 0) {
       // if browser doesn't support 'selectionEnd' property on input[type="email"], use 'value.length' property instead
       carPos = email.value.length;
     }
     [].forEach.call(copyStyle, function (prop) {
       div.style[prop] = copyStyle[prop];
     });
     div.style.position = 'absolute';
     document.body.appendChild(div);
     div.textContent = email.value.substr(0, carPos);
     span.textContent = email.value.substr(carPos) || '.';
     div.appendChild(span);

     if (email.scrollWidth <= emailScrollMax) {
       caretCoords = getPosition(span);
       dFromC = screenCenter - (caretCoords.x + emailCoords.x);
       eyeLAngle = getAngle(eyeLCoords.x, eyeLCoords.y, emailCoords.x + caretCoords.x, emailCoords.y + 25);
       eyeRAngle = getAngle(eyeRCoords.x, eyeRCoords.y, emailCoords.x + caretCoords.x, emailCoords.y + 25);
       noseAngle = getAngle(noseCoords.x, noseCoords.y, emailCoords.x + caretCoords.x, emailCoords.y + 25);
       mouthAngle = getAngle(mouthCoords.x, mouthCoords.y, emailCoords.x + caretCoords.x, emailCoords.y + 25);
     } else {
       eyeLAngle = getAngle(eyeLCoords.x, eyeLCoords.y, emailCoords.x + emailScrollMax, emailCoords.y + 25);
       eyeRAngle = getAngle(eyeRCoords.x, eyeRCoords.y, emailCoords.x + emailScrollMax, emailCoords.y + 25);
       noseAngle = getAngle(noseCoords.x, noseCoords.y, emailCoords.x + emailScrollMax, emailCoords.y + 25);
       mouthAngle = getAngle(mouthCoords.x, mouthCoords.y, emailCoords.x + emailScrollMax, emailCoords.y + 25);
     }

     eyeLX = Math.cos(eyeLAngle) * 20;
     eyeLY = Math.sin(eyeLAngle) * 10;
     eyeRX = Math.cos(eyeRAngle) * 20;
     eyeRY = Math.sin(eyeRAngle) * 10;
     noseX = Math.cos(noseAngle) * 23;
     noseY = Math.sin(noseAngle) * 10;
     mouthX = Math.cos(mouthAngle) * 23;
     mouthY = Math.sin(mouthAngle) * 10;
     mouthR = Math.cos(mouthAngle) * 6;
     chinX = mouthX * .8;
     chinY = mouthY * .5;
     chinS = 1 - ((dFromC * .15) / 100);
     if (chinS > 1) {
       chinS = 1 - (chinS - 1);
       if (chinS < chinMin) {
         chinS = chinMin;
       }
     }
     faceX = mouthX * .3;
     faceY = mouthY * .4;
     faceSkew = Math.cos(mouthAngle) * 5;
     eyebrowSkew = Math.cos(mouthAngle) * 25;
     outerEarX = Math.cos(mouthAngle) * 4;
     outerEarY = Math.cos(mouthAngle) * 5;
     hairX = Math.cos(mouthAngle) * 6;
     hairS = 1.2;

     TweenMax.to(eyeL, 1, { x: -eyeLX, y: -eyeLY, ease: Expo.easeOut });
     TweenMax.to(eyeR, 1, { x: -eyeRX, y: -eyeRY, ease: Expo.easeOut });
     TweenMax.to(nose, 1, { x: -noseX, y: -noseY, rotation: mouthR, transformOrigin: "center center", ease: Expo.easeOut });
     TweenMax.to(mouth, 1, { x: -mouthX, y: -mouthY, rotation: mouthR, transformOrigin: "center center", ease: Expo.easeOut });
     TweenMax.to(chin, 1, { x: -chinX, y: -chinY, scaleY: chinS, ease: Expo.easeOut });
     TweenMax.to(face, 1, { x: -faceX, y: -faceY, skewX: -faceSkew, transformOrigin: "center top", ease: Expo.easeOut });
     TweenMax.to(eyebrow, 1, { x: -faceX, y: -faceY, skewX: -eyebrowSkew, transformOrigin: "center top", ease: Expo.easeOut });
     TweenMax.to(outerEarL, 1, { x: outerEarX, y: -outerEarY, ease: Expo.easeOut });
     TweenMax.to(outerEarR, 1, { x: outerEarX, y: outerEarY, ease: Expo.easeOut });
     TweenMax.to(earHairL, 1, { x: -outerEarX, y: -outerEarY, ease: Expo.easeOut });
     TweenMax.to(earHairR, 1, { x: -outerEarX, y: outerEarY, ease: Expo.easeOut });
     TweenMax.to(hair, 1, { x: hairX, scaleY: hairS, transformOrigin: "center bottom", ease: Expo.easeOut });

     document.body.removeChild(div);
   };

   function onEmailInput(e) {
     calculateFaceMove(e);
     var value = email.value;
     curEmailIndex = value.length;

     // very crude email validation to trigger effects
     if (curEmailIndex > 0) {
       if (mouthStatus === "small") {
         mouthStatus = "medium";
         TweenMax.to([mouthBG, mouthOutline, mouthMaskPath], 1, { morphSVG: mouthMediumBG, shapeIndex: 8, ease: Expo.easeOut });
         TweenMax.to(tooth, 1, { x: 0, y: 0, ease: Expo.easeOut });
         TweenMax.to(tongue, 1, { x: 0, y: 1, ease: Expo.easeOut });
         TweenMax.to([eyeL, eyeR], 1, { scaleX: .85, scaleY: .85, ease: Expo.easeOut });
         eyeScale = .85;
       }
       if (value.includes("@")) {
         mouthStatus = "large";
         TweenMax.to([mouthBG, mouthOutline, mouthMaskPath], 1, { morphSVG: mouthLargeBG, ease: Expo.easeOut });
         TweenMax.to(tooth, 1, { x: 3, y: -2, ease: Expo.easeOut });
         TweenMax.to(tongue, 1, { y: 2, ease: Expo.easeOut });
         TweenMax.to([eyeL, eyeR], 1, { scaleX: .65, scaleY: .65, ease: Expo.easeOut, transformOrigin: "center center" });
         eyeScale = .65;
       } else {
         mouthStatus = "medium";
         TweenMax.to([mouthBG, mouthOutline, mouthMaskPath], 1, { morphSVG: mouthMediumBG, ease: Expo.easeOut });
         TweenMax.to(tooth, 1, { x: 0, y: 0, ease: Expo.easeOut });
         TweenMax.to(tongue, 1, { x: 0, y: 1, ease: Expo.easeOut });
         TweenMax.to([eyeL, eyeR], 1, { scaleX: .85, scaleY: .85, ease: Expo.easeOut });
         eyeScale = .85;
       }
     } else {
       mouthStatus = "small";
       TweenMax.to([mouthBG, mouthOutline, mouthMaskPath], 1, { morphSVG: mouthSmallBG, shapeIndex: 9, ease: Expo.easeOut });
       TweenMax.to(tooth, 1, { x: 0, y: 0, ease: Expo.easeOut });
       TweenMax.to(tongue, 1, { y: 0, ease: Expo.easeOut });
       TweenMax.to([eyeL, eyeR], 1, { scaleX: 1, scaleY: 1, ease: Expo.easeOut });
       eyeScale = 1;
     }
   }

   function onEmailFocus(e) {
     activeElement = "email";
     e.target.parentElement.classList.add("focusWithText");
     //stopBlinking();
     //calculateFaceMove();
     onEmailInput();
   }

   function onEmailBlur(e) {
     activeElement = null;
     setTimeout(function () {
       if (activeElement === "email") {
       } else {
         if (e.target.value === "") {
           e.target.parentElement.classList.remove("focusWithText");
         }
         //startBlinking();
         resetFace();
       }
     }, 100);
   }

   function onEmailLabelClick(e) {
     activeElement = "email";
   }

   function onPasswordFocus(e) {
     activeElement = "password";
     if (!eyesCovered) {
       coverEyes();
     }
   }

   function onPasswordBlur(e) {
     activeElement = null;
     setTimeout(function () {
       if (activeElement === "toggle" || activeElement === "password") {
       } else {
         uncoverEyes();
       }
     }, 100);
   }

   function onPasswordToggleFocus(e) {
     activeElement = "toggle";
     if (!eyesCovered) {
       coverEyes();
     }
   }

   function onPasswordToggleBlur(e) {
     activeElement = null;
     if (!showPasswordClicked) {
       setTimeout(function () {
         if (activeElement === "password" || activeElement === "toggle") {
         } else {
           uncoverEyes();
         }
       }, 100);
     }
   }

   function onPasswordToggleMouseDown(e) {
     showPasswordClicked = true;
   }

   function onPasswordToggleMouseUp(e) {
     showPasswordClicked = false;
   }

   function onPasswordToggleChange(e) {
     setTimeout(function () {
       // if checkbox is checked, show password
       if (e.target.checked) {
         password.type = "text";
         spreadFingers();

         // if checkbox is off, hide password
       } else {
         password.type = "password";
         closeFingers();
       }
     }, 100);
   }

   function onPasswordToggleClick(e) {
     //console.log("click: " + e.target.id);
     e.target.focus();
   }

   function spreadFingers() {
     TweenMax.to(twoFingers, .35, { transformOrigin: "bottom left", rotation: 30, x: -9, y: -2, ease: Power2.easeInOut });
   }

   function closeFingers() {
     TweenMax.to(twoFingers, .35, { transformOrigin: "bottom left", rotation: 0, x: 0, y: 0, ease: Power2.easeInOut });
   }

   function coverEyes() {
     TweenMax.killTweensOf([armL, armR]);
     TweenMax.set([armL, armR], { visibility: "visible" });
     TweenMax.to(armL, .45, { x: -93, y: 10, rotation: 0, ease: Quad.easeOut });
     TweenMax.to(armR, .45, { x: -93, y: 10, rotation: 0, ease: Quad.easeOut, delay: .1 });
     TweenMax.to(bodyBG, .45, { morphSVG: bodyBGchanged, ease: Quad.easeOut });
     eyesCovered = true;
   }

   function uncoverEyes() {
     TweenMax.killTweensOf([armL, armR]);
     TweenMax.to(armL, 1.35, { y: 220, ease: Quad.easeOut });
     TweenMax.to(armL, 1.35, { rotation: 105, ease: Quad.easeOut, delay: .1 });
     TweenMax.to(armR, 1.35, { y: 220, ease: Quad.easeOut });
     TweenMax.to(armR, 1.35, {
       rotation: -105, ease: Quad.easeOut, delay: .1, onComplete: function () {
         TweenMax.set([armL, armR], { visibility: "hidden" });
       }
     });
     TweenMax.to(bodyBG, .45, { morphSVG: bodyBG, ease: Quad.easeOut });
     eyesCovered = false;
   }

   function resetFace() {
     TweenMax.to([eyeL, eyeR], 1, { x: 0, y: 0, ease: Expo.easeOut });
     TweenMax.to(nose, 1, { x: 0, y: 0, scaleX: 1, scaleY: 1, ease: Expo.easeOut });
     TweenMax.to(mouth, 1, { x: 0, y: 0, rotation: 0, ease: Expo.easeOut });
     TweenMax.to(chin, 1, { x: 0, y: 0, scaleY: 1, ease: Expo.easeOut });
     TweenMax.to([face, eyebrow], 1, { x: 0, y: 0, skewX: 0, ease: Expo.easeOut });
     TweenMax.to([outerEarL, outerEarR, earHairL, earHairR, hair], 1, { x: 0, y: 0, scaleY: 1, ease: Expo.easeOut });
   }

   function startBlinking(delay) {
     if (delay) {
       delay = getRandomInt(delay);
     } else {
       delay = 1;
     }
     blinking = TweenMax.to([eyeL, eyeR], .1, {
       delay: delay, scaleY: 0, yoyo: true, repeat: 1, transformOrigin: "center center", onComplete: function () {
         startBlinking(12);
       }
     });
   }

   function stopBlinking() {
     blinking.kill();
     blinking = null;
     TweenMax.set([eyeL, eyeR], { scaleY: eyeScale });
   }

   function getRandomInt(max) {
     return Math.floor(Math.random() * Math.floor(max));
   }

   function getAngle(x1, y1, x2, y2) {
     var angle = Math.atan2(y1 - y2, x1 - x2);
     return angle;
   }

   function getPosition(el) {
     var xPos = 0;
     var yPos = 0;

     while (el) {
       if (el.tagName === "BODY") {
         // deal with browser quirks with body/window/document and page scroll
         var xScroll = el.scrollLeft || document.documentElement.scrollLeft;
         var yScroll = el.scrollTop || document.documentElement.scrollTop;

         xPos += (el.offsetLeft - xScroll + el.clientLeft);
         yPos += (el.offsetTop - yScroll + el.clientTop);
       } else {
         // for all other non-BODY elements
         xPos += (el.offsetLeft - el.scrollLeft + el.clientLeft);
         yPos += (el.offsetTop - el.scrollTop + el.clientTop);
       }

       el = el.offsetParent;
     }
     //console.log("xPos: " + xPos + ", yPos: " + yPos);
     return {
       x: xPos,
       y: yPos
     };
   }

   function isMobileDevice() {
     var check = false;
     (function (a) { if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) check = true; })(navigator.userAgent || navigator.vendor || window.opera);
     return check;
   };

   function initLoginForm() {
     // some measurements for the svg's elements
     svgCoords = getPosition(mySVG);
     emailCoords = getPosition(email);
     screenCenter = svgCoords.x + (mySVG.offsetWidth / 2);
     eyeLCoords = { x: svgCoords.x + 84, y: svgCoords.y + 76 };
     eyeRCoords = { x: svgCoords.x + 113, y: svgCoords.y + 76 };
     noseCoords = { x: svgCoords.x + 97, y: svgCoords.y + 81 };
     mouthCoords = { x: svgCoords.x + 100, y: svgCoords.y + 100 };

     // handle events for email input
     email.addEventListener('focus', onEmailFocus);
     email.addEventListener('blur', onEmailBlur);
     email.addEventListener('input', onEmailInput);
     emailLabel.addEventListener('click', onEmailLabelClick);

     // handle events for password input
     password.addEventListener('focus', onPasswordFocus);
     password.addEventListener('blur', onPasswordBlur);
     //passwordLabel.addEventListener('click', onPasswordLabelClick);

     // handle events for password checkbox
     showPasswordCheck.addEventListener('change', onPasswordToggleChange);
     showPasswordCheck.addEventListener('focus', onPasswordToggleFocus);
     showPasswordCheck.addEventListener('blur', onPasswordToggleBlur);
     showPasswordCheck.addEventListener('click', onPasswordToggleClick);
     showPasswordToggle.addEventListener('mouseup', onPasswordToggleMouseUp);
     showPasswordToggle.addEventListener('mousedown', onPasswordToggleMouseDown);

     // move arms to initial positions
     TweenMax.set(armL, { x: -93, y: 220, rotation: 105, transformOrigin: "top left" });
     TweenMax.set(armR, { x: -93, y: 220, rotation: -105, transformOrigin: "top right" });

     // set initial mouth property (fixes positioning bug)
     TweenMax.set(mouth, { transformOrigin: "center center" });

     // activate blinking
     startBlinking(5);

     // determine how far email input can go before scrolling occurs
     // will be used as the furthest point avatar will look to the right
     emailScrollMax = email.scrollWidth;

     // check if we're on mobile/tablet, if so then show password initially
     if (isMobileDevice()) {
       password.type = "text";
       showPasswordCheck.checked = true;
       TweenMax.set(twoFingers, { transformOrigin: "bottom left", rotation: 30, x: -9, y: -2, ease: Power2.easeInOut });
     }

     // clear the console
     console.clear();
   }

   initLoginForm();
 }

 
 render() { 
   return (
     <div className="App">
       <form>
         <div className="svgContainer">
           <div>
             <svg className="mySVG" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
               <defs>
                 <circle id="armMaskPath" cx="100" cy="100" r="100" />
               </defs>
               <clipPath id="armMask">
                 <use xlinkHref="#armMaskPath" overflow="visible" />
               </clipPath>
               <circle cx="100" cy="100" r="100" fill="#a9ddf3" />
               <g className="body">
                 <path className="bodyBGchanged" style={{ display: 'none' }} fill="#FFFFFF" d="M200,122h-35h-14.9V72c0-27.6-22.4-50-50-50s-50,22.4-50,50v50H35.8H0l0,91h200L200,122z" />
                 <path className="bodyBGnormal" stroke="#3A5E77" stroke-width="2.5" stroke-linecap="round" stroke-linejoinn="round" fill="#FFFFFF" d="M200,158.5c0-20.2-14.8-36.5-35-36.5h-14.9V72.8c0-27.4-21.7-50.4-49.1-50.8c-28-0.5-50.9,22.1-50.9,50v50 H35.8C16,122,0,138,0,157.8L0,213h200L200,158.5z" />
                 <path fill="#DDF1FA" d="M100,156.4c-22.9,0-43,11.1-54.1,27.7c15.6,10,34.2,15.9,54.1,15.9s38.5-5.8,54.1-15.9 C143,167.5,122.9,156.4,100,156.4z" />
               </g>
               <g className="earL">
                 <g className="outerEar" fill="#ddf1fa" stroke="#3a5e77" stroke-width="2.5">
                   <circle cx="47" cy="83" r="11.5" />
                   <path d="M46.3 78.9c-2.3 0-4.1 1.9-4.1 4.1 0 2.3 1.9 4.1 4.1 4.1" stroke-linecap="round" stroke-linejoin="round" />
                 </g>
                 <g className="earHair">
                   <rect x="51" y="64" fill="#FFFFFF" width="15" height="35" />
                   <path d="M53.4 62.8C48.5 67.4 45 72.2 42.8 77c3.4-.1 6.8-.1 10.1.1-4 3.7-6.8 7.6-8.2 11.6 2.1 0 4.2 0 6.3.2-2.6 4.1-3.8 8.3-3.7 12.5 1.2-.7 3.4-1.4 5.2-1.9" fill="#fff" stroke="#3a5e77" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" />
                 </g>
               </g>
               <g className="earR">
                 <g className="outerEar">
                   <circle fill="#DDF1FA" stroke="#3A5E77" stroke-width="2.5" cx="153" cy="83" r="11.5" />
                   <path fill="#DDF1FA" stroke="#3A5E77" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" d="M153.7,78.9 c2.3,0,4.1,1.9,4.1,4.1c0,2.3-1.9,4.1-4.1,4.1" />
                 </g>
                 <g className="earHair">
                   <rect x="134" y="64" fill="#FFFFFF" width="15" height="35" />
                   <path fill="#FFFFFF" stroke="#3A5E77" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" d="M146.6,62.8 c4.9,4.6,8.4,9.4,10.6,14.2c-3.4-0.1-6.8-0.1-10.1,0.1c4,3.7,6.8,7.6,8.2,11.6c-2.1,0-4.2,0-6.3,0.2c2.6,4.1,3.8,8.3,3.7,12.5 c-1.2-0.7-3.4-1.4-5.2-1.9" />
                 </g>
               </g>
               <path className="chin" d="M84.1 121.6c2.7 2.9 6.1 5.4 9.8 7.5l.9-4.5c2.9 2.5 6.3 4.8 10.2 6.5 0-1.9-.1-3.9-.2-5.8 3 1.2 6.2 2 9.7 2.5-.3-2.1-.7-4.1-1.2-6.1" fill="none" stroke="#3a5e77" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" />
               <path className="face" fill="#DDF1FA" d="M134.5,46v35.5c0,21.815-15.446,39.5-34.5,39.5s-34.5-17.685-34.5-39.5V46" />
               <path className="hair" fill="#FFFFFF" stroke="#3A5E77" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" d="M81.457,27.929 c1.755-4.084,5.51-8.262,11.253-11.77c0.979,2.565,1.883,5.14,2.712,7.723c3.162-4.265,8.626-8.27,16.272-11.235 c-0.737,3.293-1.588,6.573-2.554,9.837c4.857-2.116,11.049-3.64,18.428-4.156c-2.403,3.23-5.021,6.391-7.852,9.474" />
               <g className="eyebrow">
                 <path fill="#FFFFFF" d="M138.142,55.064c-4.93,1.259-9.874,2.118-14.787,2.599c-0.336,3.341-0.776,6.689-1.322,10.037 c-4.569-1.465-8.909-3.222-12.996-5.226c-0.98,3.075-2.07,6.137-3.267,9.179c-5.514-3.067-10.559-6.545-15.097-10.329 c-1.806,2.889-3.745,5.73-5.816,8.515c-7.916-4.124-15.053-9.114-21.296-14.738l1.107-11.768h73.475V55.064z" />
                 <path fill="#FFFFFF" stroke="#3A5E77" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" d="M63.56,55.102 c6.243,5.624,13.38,10.614,21.296,14.738c2.071-2.785,4.01-5.626,5.816-8.515c4.537,3.785,9.583,7.263,15.097,10.329 c1.197-3.043,2.287-6.104,3.267-9.179c4.087,2.004,8.427,3.761,12.996,5.226c0.545-3.348,0.986-6.696,1.322-10.037 c4.913-0.481,9.857-1.34,14.787-2.599" />
               </g>
               <g className="eyeL">
                 <circle cx="85.5" cy="78.5" r="3.5" fill="#3a5e77" />
                 <circle cx="84" cy="76" r="1" fill="#fff" />
               </g>
               <g className="eyeR">
                 <circle cx="114.5" cy="78.5" r="3.5" fill="#3a5e77" />
                 <circle cx="113" cy="76" r="1" fill="#fff" />
               </g>
               <g className="mouth">
                 <path className="mouthBG" fill="#617E92" d="M100.2,101c-0.4,0-1.4,0-1.8,0c-2.7-0.3-5.3-1.1-8-2.5c-0.7-0.3-0.9-1.2-0.6-1.8 c0.2-0.5,0.7-0.7,1.2-0.7c0.2,0,0.5,0.1,0.6,0.2c3,1.5,5.8,2.3,8.6,2.3s5.7-0.7,8.6-2.3c0.2-0.1,0.4-0.2,0.6-0.2 c0.5,0,1,0.3,1.2,0.7c0.4,0.7,0.1,1.5-0.6,1.9c-2.6,1.4-5.3,2.2-7.9,2.5C101.7,101,100.5,101,100.2,101z" />
                 <path style={{ display: 'none' }} className="mouthSmallBG" fill="#617E92" d="M100.2,101c-0.4,0-1.4,0-1.8,0c-2.7-0.3-5.3-1.1-8-2.5c-0.7-0.3-0.9-1.2-0.6-1.8 c0.2-0.5,0.7-0.7,1.2-0.7c0.2,0,0.5,0.1,0.6,0.2c3,1.5,5.8,2.3,8.6,2.3s5.7-0.7,8.6-2.3c0.2-0.1,0.4-0.2,0.6-0.2 c0.5,0,1,0.3,1.2,0.7c0.4,0.7,0.1,1.5-0.6,1.9c-2.6,1.4-5.3,2.2-7.9,2.5C101.7,101,100.5,101,100.2,101z" />
                 <path style={{ display: 'none' }} className="mouthMediumBG" d="M95,104.2c-4.5,0-8.2-3.7-8.2-8.2v-2c0-1.2,1-2.2,2.2-2.2h22c1.2,0,2.2,1,2.2,2.2v2 c0,4.5-3.7,8.2-8.2,8.2H95z" />
                 <path style={{ display: 'none' }} className="mouthLargeBG" d="M100 110.2c-9 0-16.2-7.3-16.2-16.2 0-2.3 1.9-4.2 4.2-4.2h24c2.3 0 4.2 1.9 4.2 4.2 0 9-7.2 16.2-16.2 16.2z" fill="#617e92" stroke="#3a5e77" stroke-linejoin="round" stroke-width="2.5" />
                 <defs>
                   <path id="mouthMaskPath" d="M100.2,101c-0.4,0-1.4,0-1.8,0c-2.7-0.3-5.3-1.1-8-2.5c-0.7-0.3-0.9-1.2-0.6-1.8 c0.2-0.5,0.7-0.7,1.2-0.7c0.2,0,0.5,0.1,0.6,0.2c3,1.5,5.8,2.3,8.6,2.3s5.7-0.7,8.6-2.3c0.2-0.1,0.4-0.2,0.6-0.2 c0.5,0,1,0.3,1.2,0.7c0.4,0.7,0.1,1.5-0.6,1.9c-2.6,1.4-5.3,2.2-7.9,2.5C101.7,101,100.5,101,100.2,101z" />
                 </defs>
                 <clipPath id="mouthMask">
                   <use xlinkHref="#mouthMaskPath" overflow="visible" />
                 </clipPath>
                 <g clip-path="url(#mouthMask)">
                   <g className="tongue">
                     <circle cx="100" cy="107" r="8" fill="#cc4a6c" />
                     <ellipse className="tongueHighlight" cx="100" cy="100.5" rx="3" ry="1.5" opacity=".1" fill="#fff" />
                   </g>
                 </g>
                 <path clip-path="url(#mouthMask)" className="tooth" style={{ fill: '#FFFFFF' }} d="M106,97h-4c-1.1,0-2-0.9-2-2v-2h8v2C108,96.1,107.1,97,106,97z" />
                 <path className="mouthOutline" fill="none" stroke="#3A5E77" stroke-width="2.5" stroke-linejoin="round" d="M100.2,101c-0.4,0-1.4,0-1.8,0c-2.7-0.3-5.3-1.1-8-2.5c-0.7-0.3-0.9-1.2-0.6-1.8 c0.2-0.5,0.7-0.7,1.2-0.7c0.2,0,0.5,0.1,0.6,0.2c3,1.5,5.8,2.3,8.6,2.3s5.7-0.7,8.6-2.3c0.2-0.1,0.4-0.2,0.6-0.2 c0.5,0,1,0.3,1.2,0.7c0.4,0.7,0.1,1.5-0.6,1.9c-2.6,1.4-5.3,2.2-7.9,2.5C101.7,101,100.5,101,100.2,101z" />
               </g>
               <path className="nose" d="M97.7 79.9h4.7c1.9 0 3 2.2 1.9 3.7l-2.3 3.3c-.9 1.3-2.9 1.3-3.8 0l-2.3-3.3c-1.3-1.6-.2-3.7 1.8-3.7z" fill="#3a5e77" />
               <g className="arms" clip-path="url(#armMask)">
                 <g className="armL" style={{ visibility: 'hidden' }}>
                   <polygon fill="#DDF1FA" stroke="#3A5E77" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="121.3,98.4 111,59.7 149.8,49.3 169.8,85.4" />
                   <path fill="#DDF1FA" stroke="#3A5E77" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M134.4,53.5l19.3-5.2c2.7-0.7,5.4,0.9,6.1,3.5v0c0.7,2.7-0.9,5.4-3.5,6.1l-10.3,2.8" />
                   <path fill="#DDF1FA" stroke="#3A5E77" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M150.9,59.4l26-7c2.7-0.7,5.4,0.9,6.1,3.5v0c0.7,2.7-0.9,5.4-3.5,6.1l-21.3,5.7" />

                   <g className="twoFingers">
                     <path fill="#DDF1FA" stroke="#3A5E77" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M158.3,67.8l23.1-6.2c2.7-0.7,5.4,0.9,6.1,3.5v0c0.7,2.7-0.9,5.4-3.5,6.1l-23.1,6.2" />
                     <path fill="#A9DDF3" d="M180.1,65l2.2-0.6c1.1-0.3,2.2,0.3,2.4,1.4v0c0.3,1.1-0.3,2.2-1.4,2.4l-2.2,0.6L180.1,65z" />
                     <path fill="#DDF1FA" stroke="#3A5E77" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M160.8,77.5l19.4-5.2c2.7-0.7,5.4,0.9,6.1,3.5v0c0.7,2.7-0.9,5.4-3.5,6.1l-18.3,4.9" />
                     <path fill="#A9DDF3" d="M178.8,75.7l2.2-0.6c1.1-0.3,2.2,0.3,2.4,1.4v0c0.3,1.1-0.3,2.2-1.4,2.4l-2.2,0.6L178.8,75.7z" />
                   </g>
                   <path fill="#A9DDF3" d="M175.5,55.9l2.2-0.6c1.1-0.3,2.2,0.3,2.4,1.4v0c0.3,1.1-0.3,2.2-1.4,2.4l-2.2,0.6L175.5,55.9z" />
                   <path fill="#A9DDF3" d="M152.1,50.4l2.2-0.6c1.1-0.3,2.2,0.3,2.4,1.4v0c0.3,1.1-0.3,2.2-1.4,2.4l-2.2,0.6L152.1,50.4z" />
                   <path fill="#FFFFFF" stroke="#3A5E77" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" d="M123.5,97.8 c-41.4,14.9-84.1,30.7-108.2,35.5L1.2,81c33.5-9.9,71.9-16.5,111.9-21.8" />
                   <path fill="#FFFFFF" stroke="#3A5E77" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" d="M108.5,60.4 c7.7-5.3,14.3-8.4,22.8-13.2c-2.4,5.3-4.7,10.3-6.7,15.1c4.3,0.3,8.4,0.7,12.3,1.3c-4.2,5-8.1,9.6-11.5,13.9 c3.1,1.1,6,2.4,8.7,3.8c-1.4,2.9-2.7,5.8-3.9,8.5c2.5,3.5,4.6,7.2,6.3,11c-4.9-0.8-9-0.7-16.2-2.7" />
                   <path fill="#FFFFFF" stroke="#3A5E77" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" d="M94.5,103.8 c-0.6,4-3.8,8.9-9.4,14.7c-2.6-1.8-5-3.7-7.2-5.7c-2.5,4.1-6.6,8.8-12.2,14c-1.9-2.2-3.4-4.5-4.5-6.9c-4.4,3.3-9.5,6.9-15.4,10.8 c-0.2-3.4,0.1-7.1,1.1-10.9" />
                   <path fill="#FFFFFF" stroke="#3A5E77" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" d="M97.5,63.9 c-1.7-2.4-5.9-4.1-12.4-5.2c-0.9,2.2-1.8,4.3-2.5,6.5c-3.8-1.8-9.4-3.1-17-3.8c0.5,2.3,1.2,4.5,1.9,6.8c-5-0.6-11.2-0.9-18.4-1 c2,2.9,0.9,3.5,3.9,6.2" />
                 </g>
                 <g className="armR" style={{ visibility: 'hidden' }}>
                   <path fill="#ddf1fa" stroke="#3a5e77" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2.5" d="M265.4 97.3l10.4-38.6-38.9-10.5-20 36.1z" />
                   <path fill="#ddf1fa" stroke="#3a5e77" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2.5" d="M252.4 52.4L233 47.2c-2.7-.7-5.4.9-6.1 3.5-.7 2.7.9 5.4 3.5 6.1l10.3 2.8M226 76.4l-19.4-5.2c-2.7-.7-5.4.9-6.1 3.5-.7 2.7.9 5.4 3.5 6.1l18.3 4.9M228.4 66.7l-23.1-6.2c-2.7-.7-5.4.9-6.1 3.5-.7 2.7.9 5.4 3.5 6.1l23.1 6.2M235.8 58.3l-26-7c-2.7-.7-5.4.9-6.1 3.5-.7 2.7.9 5.4 3.5 6.1l21.3 5.7" />
                   <path fill="#a9ddf3" d="M207.9 74.7l-2.2-.6c-1.1-.3-2.2.3-2.4 1.4-.3 1.1.3 2.2 1.4 2.4l2.2.6 1-3.8zM206.7 64l-2.2-.6c-1.1-.3-2.2.3-2.4 1.4-.3 1.1.3 2.2 1.4 2.4l2.2.6 1-3.8zM211.2 54.8l-2.2-.6c-1.1-.3-2.2.3-2.4 1.4-.3 1.1.3 2.2 1.4 2.4l2.2.6 1-3.8zM234.6 49.4l-2.2-.6c-1.1-.3-2.2.3-2.4 1.4-.3 1.1.3 2.2 1.4 2.4l2.2.6 1-3.8z" />
                   <path fill="#fff" stroke="#3a5e77" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M263.3 96.7c41.4 14.9 84.1 30.7 108.2 35.5l14-52.3C352 70 313.6 63.5 273.6 58.1" />
                   <path fill="#fff" stroke="#3a5e77" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M278.2 59.3l-18.6-10 2.5 11.9-10.7 6.5 9.9 8.7-13.9 6.4 9.1 5.9-13.2 9.2 23.1-.9M284.5 100.1c-.4 4 1.8 8.9 6.7 14.8 3.5-1.8 6.7-3.6 9.7-5.5 1.8 4.2 5.1 8.9 10.1 14.1 2.7-2.1 5.1-4.4 7.1-6.8 4.1 3.4 9 7 14.7 11 1.2-3.4 1.8-7 1.7-10.9M314 66.7s5.4-5.7 12.6-7.4c1.7 2.9 3.3 5.7 4.9 8.6 3.8-2.5 9.8-4.4 18.2-5.7.1 3.1.1 6.1 0 9.2 5.5-1 12.5-1.6 20.8-1.9-1.4 3.9-2.5 8.4-2.5 8.4" />
                 </g>
               </g>

             </svg>
           </div>
         </div>

         <div className="inputGroup inputGroup1">
           <label for="loginEmail" id="loginEmailLabel">Email</label>
           <input type="email" id="loginEmail" maxlength="254" />
           <p className="helper helper1">email@domain.com</p>
         </div>
         <div className="inputGroup inputGroup2">
           <label for="loginPassword" id="loginPasswordLabel">Password</label>
           <input type="password" id="loginPassword" />
           <label id="showPasswordToggle" for="showPasswordCheck">Show
               <input id="showPasswordCheck" type="checkbox" />
             <div className="indicator"></div>
           </label>
         </div>
         <div className="inputGroup inputGroup3">
           <button id="login">Log in</button>
         </div>
       </form>
     </div>
   );
 }
}

export default App;

Tiếp theo thêm file scss, bạn tạo mới file src/App.scss và thêm vào nội dung sau


/* colors */
$darkBlue: #217093;
$medBlue: #4eb8dd;
$lightBlue: #ddf1fa;
$inputBG: #f3fafd;

html {width: 100%; height: 100%;}
body {
   background-color: #eff3f4; position: relative; width: 100%; height: 100%;
   font-size: 16px; font-family: 'Source Sans Pro', sans-serif; font-weight: 400;
   -webkit-font-smoothing: antialiased;
}
form {
   position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%);
   display: block; width: 100%; max-width: 400px; background-color: #FFF;
   margin: 0; padding: 2.25em; box-sizing: border-box; border: solid 1px #DDD; border-radius: .5em;
   font-family: 'Source Sans Pro', sans-serif;
   .svgContainer {
   	position: relative; width: 200px; height: 200px; margin: 0 auto 1em;
   	border-radius: 50%;
   	pointer-events: none;
   	div {
   		position: relative; width: 100%; height: 0; overflow: hidden; border-radius: 50%;
   		padding-bottom: 100%;
   	}
   	.mySVG {
   		position: absolute; left: 0; top: 0; width: 100%; height: 100%;
   		pointer-events: none;
   	}
   	&:after {
   		content: ""; position: absolute; top: 0; left: 0; z-index: 10; 
   		width: inherit; height: inherit; box-sizing: border-box;
   		border: solid 2.5px $darkBlue; border-radius: 50%;
   	}
   }
   .inputGroup {
   	margin: 0 0 2em; padding: 0; position: relative;
   	&:last-of-type {
   		margin-bottom: 0;
   	}
   }
   label {
   	margin: 0 0 12px; display: block;
   	font-size: 1.25em; color: #217093; font-weight: 700; font-family: inherit;
   }
   input[type='email'], input[type="text"], input[type="number"], input[type="url"], input[type="search"], input[type="password"] {
   	display: block; margin: 0; padding: 0 1em 0; padding: .875em 1em 0;
   	background-color: $inputBG; border: solid 2px $darkBlue; border-radius: 4px; -webkit-appearance: none;
   	box-sizing: border-box;
   	width: 100%; height: 65px;
   	font-size: 1.55em; color: #353538; font-weight: 600; font-family: inherit;
   	transition: box-shadow .2s linear, border-color .25s ease-out;
   	&:focus {
   		outline: none;
   		box-shadow: 0px 2px 10px rgba(0,0,0,.1);
   		border: solid 2px $medBlue;
   	}
   }
   button {
   	display: block; margin: 0; padding: .65em 1em 1em;
   	background-color: $medBlue; border: none; border-radius: 4px;
   	box-sizing: border-box; box-shadow: none;
   	width: 100%; height: 65px;
   	font-size: 1.55em; color: #FFF; font-weight: 600; font-family: inherit;
   	transition: background-color .2s ease-out;
   	&:hover, &:active {
   		background-color: $darkBlue;
   	}
   }

   .inputGroup1 {
   	.helper {
   		position: absolute; z-index: 1;
   		font-family: inherit;
   	}
   	.helper1 {
   		top: 0; left: 0;
   		transform: translate(1em, 2.2em) scale(1); transform-origin: 0 0;
   		color: $darkBlue; font-size: 1.55em; font-weight: 400; opacity: .65;
   		pointer-events: none;
   		transition: transform .2s ease-out, opacity .2s linear;
   	}
   	
   	&.focusWithText .helper {
   		transform: translate(1em, 1.55em) scale(.6); opacity: 1;
   	}
   }
   .inputGroup2 {
   	input[type="password"] {
   		padding: .4em 1em .5em;
   	}
   	input[type="text"] {
   		padding: .025em 1em 0;
   	}
   	#showPasswordToggle {
   		display: block; padding: 0 0 0 1.45em;
   		position: absolute; top: .25em; right: 0;
   		font-size: 1em;
   		input {
   			position: absolute; z-index: -1;
   			opacity: 0;
   		}
   		.indicator {
   			position: absolute; top: 0; left: 0;
   			height: .85em; width: .85em;
   			background-color: $inputBG; border: solid 2px $darkBlue; border-radius: 3px;
   			&:after {
   				content: "";
   				position: absolute; left: .25em; top: .025em;
   				width: .2em; height: .5em;
   				border: solid $darkBlue; border-width: 0 3px 3px 0;
   				transform: rotate(45deg); visibility: hidden;
   			}
   		}
   		input:checked ~ .indicator {
   			&:after {visibility: visible;}
   		}
   		input:focus ~ .indicator, input:hover ~ .indicator {
   			border-color: $medBlue;
   		}
   		input:disabled ~ .indicator {
   			opacity: .5;
   			&:after {visibility: hidden;}
   		}
   	}
   }
}

p {
 margin: 0px;
}

Vậy là xong, bạn quay lại trang web localhost:300 để trải nghiệm nhé

Bạn cũng có thể tham khảo link github pages của mình: https://ththth0303.github.io/login-svg-react/

Tham khảo

Link example gốc: https://codepen.io/dsenneff/full/2c3e5bc86b372d5424b00edaf4990173

Link github sau khi chuyển sang react: https://github.com/ththth0303/login-svg-react.git


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí