全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2024-10-23_vue3+gasp实现【星之卡比输入框】

您的位置:首页 >> 新闻 >> 行业资讯

vue3+gasp实现【星之卡比输入框】 点击关注公众号,“技术干货”及时达! 前言在vue3中使用gasp去实现一个星之卡比的输入框,实现对输入框中的内容进行正则判断,根据正则结果实现不同的动画效果。 本组件是根据gasp官网的案例进行修改copy的有兴趣的话可以自己去对应的官网看看(ps:官网的是tsx的版本的,我这改成了vue3.2的setup语法糖组件)https://codepen.io/collection/naMaNQ 效果图动画.gif 技术栈技术栈官网vue3https://cn.vuejs.org/gasphttps://gsap.com/代码结构image.png一、 星之卡比外观svg的绘制外观没啥好说的直接复制就行了 //KirbyComponent.vue template div class="kirby-wrap" svg id="kirby" class="kirby" viewBox="0 0 200 200" use ref="star" href="#star" class="kirby-star-spit" / defs path ref="bodySwallow" d="M167.58,120.58c0,18-6,30.75-62.29,29.58C49,149,43,138.58,43,120.58S70.89,85,105.29,85 S167.58,102.59,167.58,120.58z" /path path id="star" class="kirby-star" d="M119.43,124.74l4.63-5.32a4.08,4.08,0,0,0-2.39-6.7l-5.23-1a.9.9,0,0,1-.56-.39l-4.67-7.69a4.28,4.28,0,0,0-7.27,0l-4.68,7.69a.85.85,0,0,1-.56.39l-5.23,1a4.09,4.09,0,0,0-2.39,6.7l4.63,5.32a.77.77,0,0,1,.18.72l-1.51,6a4.19,4.19,0,0,0,5.82,4.73l7-3a.89.89,0,0,1,.68,0l7,3a4.19,4.19,0,0,0,5.82-4.73l-1.51-6A.82.82,0,0,1,119.43,124.74Z" /path /defs g id="legLeft" ref="legRight" ellipse class="kirby-foot" cx="90" cy="141.5" rx="23" ry="26"/ellipse ellipse class="kirby-stroke" cx="85" cy="141.5" rx="23" ry="26"/ellipse g id="legRight" data-svg-origin="102 97.5" transform="matrix(1,0,0,1,0,0)" style="translate: none; rotate: none; scale: none; transform-origin: 0px 0px" ellipse class="kirby-foot" cx="138" cy="134.5" rx="23" ry="26"/ellipse ellipse class="kirby-stroke" cx="138" cy="129.5" rx="23" ry="26"/ellipse g id="armLeft" ref="armLeft" data-svg-origin="100 97.5" transform="matrix(1,0,0,1,0,0)" style="translate: none; rotate: none; scale: none; transform-origin: 0px 0px" circle id="armLeft-2" class="kirby-body" cx="51.5" cy="76" r="20.5"/circle circle class="kirby-stroke" cx="50.5" cy="82" r="20.5"/circle g id="body" ref="body" data-svg-origin="43 153" transform="matrix(1,0,0,1,0,0)" style="translate: none; rotate: none; scale: none; transform-origin: 0px 0px" path class="kirby-body" id="bodyFill" ref="bodyFill" d="M159,95.5 C159,127.25637 133.25637,153 101.5,153 69.74363,153 44,127.25637 44,95.5 44,63.74363 69.74363,38 101.5,38 133.25637,38 159,63.74363 159,95.5 z" /path path class="kirby-stroke" id="bodyStroke" ref="bodyStroke" d="M158,91.5 C158,123.25637 132.25637,149 100.5,149 68.74363,149 43,123.25637 43,91.5 43,59.74363 68.74363,34 100.5,34 132.25637,34 158,59.74363 158,91.5 z" /path g id="armRight" ref="armRight" data-svg-origin="101 97.49999618530273" transform="matrix(1,0,0,1,0,0)" style="translate: none; rotate: none; scale: none; transform-origin: 0px 0px" path class="kirby-body" d="M141,58.73A20.5,20.5,0,1,1,154.68,94.5"/path path class="kirby-stroke" d="M136,58.73A20.5,20.5,0,1,1,149.68,94.5"/path g id="face" ref="face" data-svg-origin="72 125.12000274658203" transform="matrix(1,0,0,1,0,0)" style="translate: none; rotate: none; scale: none; transform-origin: 0px 0px" path id="eyeRight" ref="eyeRight" class="kirby-eye" d="M84,84.53c0,5.8-1.57,9.47-3.5,9.47S77,90.33,77,84.53,78.57,73,80.5,73,84,78.73,84,84.53Z" data-svg-origin="80.5 90" transform="matrix(1,0,0,1,0,0)" style="translate: none; rotate: none; scale: none; transform-origin: 0px 0px" /path path id="eyeLeft" ref="eyeLeft" class="kirby-eye" d="M68,84.53c0,5.8-1.57,9.47-3.5,9.47S61,90.33,61,84.53,62.57,73,64.5,73,68,78.73,68,84.53Z" data-svg-origin="64.5 90" transform="matrix(1,0,0,1,0,0)" style="translate: none; rotate: none; scale: none; transform-origin: 0px 0px" /path path id="eyeRightClosed" ref="eyeRightClosed" class="kirby-stroke" d="M80.69,125.12c.37-1.41,1.89-3.8,4-6.35a22.84,22.84,0,0,1,5.61-5.14" data-svg-origin="85.49500274658203 125.12000274658203" transform="matrix(1,0,0,0,0,125.12)" style="translate: none; rotate: none; scale: none; transform-origin: 0px 0px" /path path id="eyeLeftClosed" ref="eyeLeftClosed" class="kirby-stroke" d="M55.05,113.61c1.33.61,3.42,2.52,5.56,5.07a23.08,23.08,0,0,1,4.09,6.42" data-svg-origin="59.87499809265137 125.0999984741211" transform="matrix(1,0,0,0,0,125.1)" style="translate: none; rotate: none; scale: none; transform-origin: 0px 0px" /path g ref="cheeks" path id="cheekRight" class="kirby-blush kirby-stroke" d="M93,92.94A22.41,22.41,0,0,1,101.66,89l1.09,5.41s2.74-2.94,8.66-3.93" /path path id="cheekLeft" class="kirby-blush kirby-blush-small kirby-stroke" d="M47,92.94A11,11,0,0,1,51.19,89l.52,5.41a8,8,0,0,1,4.19-3.93" /path path id="mouth" ref="mouth" class="kirby-stroke" d="M77,98c0,1.93-2.12,3.5-4.5,3.5S68,99.93,68,98" /path g id="mouthOpen" ref="mouthOpen" class="kirby-mouth-open" data-svg-origin="71 103" transform="matrix(0,0,0,0,71,103)" style="translate: none; rotate: none; scale: none; transform-origin: 0px 0px" path class="kirby-stroke kirby-mouth-open-inner" d="M94,105.69c0,14.36-11,23.31-22,23.31s-18-9-18-23.31S61,77,72,77,94,91.33,94,105.69Z" /path path class="kirby-stroke kirby-mouth-open-tongue" d="M72,129a22,22,0,0,0,18.91-11.18C90,109.51,82.8,101.07,74,101.07c-9.39,0-17,9.6-17,18.41,0,.11,0,.21,0,.32A16.16,16.16,0,0,0,72,129Z" /path g id="mouthFull" ref="mouthFull" class="kirby-mouth-full" data-svg-origin="73.31500244140625 101.76000213623047" transform="matrix(0,0,0,0,73.315,101.76)" style="translate: none; rotate: none; scale: none; transform-origin: 0px 0px" path class="kirby-stroke" d="M68.45,101.5a6.42,6.42,0,0,1,4-1,7.1,7.1,0,0,1,4,1" /path path class="kirby-stroke kirby-mouth-cheek" d="M78.07,106.19a5.17,5.17,0,0,1-1.2-4.75,5.63,5.63,0,0,1,2.76-4.11" /path path class="kirby-stroke kirby-mouth-cheek" d="M67,99a4,4,0,0,1,1.5,2.76,4.31,4.31,0,0,1-.46,3.15" /path path id="mouthFrown" ref="mouthFrown" class="kirby-stroke" d="M70,133.5a2.2,2.2,0,0,1,2-1,2.4,2.4,0,0,1,2,1" data-svg-origin="72 132.99735260009766" transform="matrix(1,0,0,0,0,132.99735)" style="translate: none; rotate: none; scale: none; transform-origin: 0px 0px" /path g id="hat" ref="hat" data-svg-origin="0 0" transform="matrix(0,0,0,0,0,0)" style="translate: none; rotate: none; scale: none; transform-origin: 0px 0px" /g g id="envelope" ref="envelope" data-svg-origin="164.17000579833984 54" transform="matrix(0,0,0,0,164.17001,64)" style="translate: none; rotate: none; scale: none; transform-origin: 0px 0px" rect class="kirby-envelope" x="130.99" y="9" width="67.5" height="45" rx="10" /rect rect class="kirby-stroke" x="129.85" y="3" width="67.5" height="45" rx="10" /rect polyline class="kirby-stroke" points="131.85 10 163.6 31.5 195.35 10"/polyline path class="kirby-envelope-star kirby-stroke" ref="envelopeStar" d="M170,31l2.48-2.93a2.26,2.26,0,0,0-1.28-3.68l-2.8-.56a.46.46,0,0,1-.3-.22l-2.51-4.23a2.26,2.26,0,0,0-3.89,0l-2.51,4.23a.46.46,0,0,1-.3.22l-2.8.56a2.26,2.26,0,0,0-1.28,3.68L157.25,31a.45.45,0,0,1,.1.4l-.81,3.32a2.26,2.26,0,0,0,3.12,2.6l3.76-1.67a.48.48,0,0,1,.37,0l3.76,1.67a2.26,2.26,0,0,0,3.12-2.6l-.81-3.32A.45.45,0,0,1,170,31Z" data-svg-origin="163.64500427246094 27.894429206848145" transform="matrix(0,0,0,0,163.645,27.89443)" style="translate: none; rotate: none; scale: none; transform-origin: 0px 0px" /path /svg /div /template script/script style lang="scss" $black: #87213c; $white: #fff; $primary-color: #f9dde3; $primary-color-tint: #fcf2f5; $secondary-color: #ff2961; $accent-color: #fae798; $mail-color: #326dcc; $star-sparkly-color: #b8d7ff; $primary-font: 'Fredoka One', sans-serif; :root { --black: #{$black}; --white: #{$white}; --primary-color: #{$primary-color}; --primary-color-tint: #{$primary-color-tint}; --secondary-color: #{$secondary-color}; --accent-color: #{$accent-color}; --mail-color: #{$mail-color}; --star-color: #{$accent-color}; --star-sparkly-color: #{$star-sparkly-color}; } @import url('https://fonts.googleapis.com/css2?family=Fredoka+One&display=swap'); * { box-sizing: border-box; } body { display: grid; place-items: center; min-height: 100vh; background-color: var(--primary-color-tint); font-size: 16px; line-height: 1; font-family: $primary-font; } .container { display: grid; grid-template-columns: 2fr 1fr; align-items: center; width: 400px; max-width: 400px; } .input-text { position: absolute; z-index: 5; width: 17rem; font-size: 1.25rem; line-height: 1.5; color: var(--black); transform: translate(1rem, 0.625rem); overflow: hidden; &-letter { display: inline-block; transform-origin: center bottom; } } .input-wrap { position: relative; } .cloudy { position: absolute; top: 50%; left: 50%; width: 150%; transform: translate(-50%, -50%); &-poofs { fill: var(--white); } } .fade { &-enter-active, &-leave-active { transition: 0.3s; } &-enter-from, &-leave-to { opacity: 0; } } .dreamy-input { --offset-top: 0.1875rem; --offset-left: -0.125rem; --control-height: 3.125rem; position: relative; &::after { content: ''; position: absolute; top: var(--offset-top); left: var(--offset-left); height: 100%; width: 100%; border: 3px solid var(--black); pointer-events: none; transition: border 0.5s ease-in-out; } &::after, &-control { border-radius: var(--control-height); } &-control { width: 15.625rem; height: var(--control-height); padding: 0 calc(1rem + var(--control-height)) 0 1rem; border: 0; background-color: var(--primary-color); font-family: $primary-font; font-size: 1.25rem; color: var(--black); caret-color: var(--secondary-color); transition: background-color 0.5s ease-in-out; &:focus { outline: none; } &:disabled { background-color: var(--primary-color-tint); } } &:focus-within { &::after { border-color: var(--secondary-color); } } &-label, &-error { position: absolute; left: 1rem; font-size: 0.75rem; letter-spacing: 0.05em; } &-label { top: -0.75rem; color: var(--black); } &-error { bottom: -1.5rem; color: var(--secondary-color); } &-button { display: grid; place-items: center; position: absolute; top: calc(var(--offset-top) + 3px); right: 0; height: var(--control-height); width: var(--control-height); padding: 0; border: 0; background-color: transparent; color: var(--white); cursor: pointer; transition: 0.4s ease-in-out; &-star { fill: currentColor; stroke: var(--star-stroke, transparent); stroke-width: 3px; } &-svg { width: 40%; overflow: visible; } &:focus { outline: none; color: var(--secondary-color); } &:hover { --star-stroke: var(--black); transform: scale(1.2); color: var(--accent-color); } } &.disabled { &::after { border-color: var(--primary-color); } .dreamy-input-button { color: var(--primary-color); } } } /style style lang="scss" scoped .kirby { position: relative; z-index: 5; display: inline-block; height: 120px; width: 120px; overflow: visible; } .kirby-stroke { stroke: var(--black); stroke-width: 3px; stroke-miterlimit: 10; stroke-linecap: round; stroke-linejoin: round; fill: none; } .kirby-body { fill: var(--primary-color); } .kirby-mouth-open-tongue, .kirby-foot { fill: var(--secondary-color); } .kirby-hat-dark, .kirby-mouth-open-inner, .kirby-eye { fill: var(--black); } .kirby-blush { stroke: var(--secondary-color); } .kirby-blush-small { stroke-width: 2.5px; } .kirby-star { stroke-width: 3px; stroke: currentColor; fill: var(--star-color); } .kirby-star-splashy { color: transparent; } .kirby-star-spit { color: var(--black); } .kirby-hat-band { fill: var(--accent-color); } .kirby-hat { fill: var(--mail-color); } .kirby-envelope { fill: var(--white); } .kirby-envelope-star { fill: var(--primary-color); }/style二、引入gasp插件以及配置获取节点的ref//KirbyComponent.vueimport { ref, watch, type Ref, onMounted } from 'vue'import { gsap } from 'gsap'import { MorphSVGPlugin } from 'gsap/MorphSVGPlugin'//gasp的额外插件需要用此方法注册gsap.registerPlugin(MorphSVGPlugin)/**接收父级组件传递过来的参数用来执行对应的动画 */const props = defineProps({ swallow: Boolean, spit: Boolean})const emits = defineEmits(['animationdone'])//用于获取svg节点const bodyPathFill: RefmorphSVGType = ref(void 0);const bodyPathStroke: RefmorphSVGType = ref(void 0);const starIsVisible = ref(0);const starPosition: RefpositionType = ref(null);const bodySwallow = ref(null) as anyconst mouth = ref(null)const mouthOpen = ref(null)const face = ref(null)const armLeft = ref(null)const armRight = ref(null)const legRight = ref(null)const mouthFull = ref(null)const body = ref(null)const envelope = ref(null)const envelopeStar = ref(null)const eyeLeft = ref(null)const eyeRight = ref(null)const eyeLeftClosed = ref(null)const eyeRightClosed = ref(null)const hat = ref(null)const star = ref(null)const mouthFrown = ref(null)const cheeks = ref(null)const bodyFill: any = ref(null)const bodyStroke: any = ref(null)三、动画方法所有的关于星之卡比的动画都在这里了(不包含输入框文字的动画),gasp的具体属性以及方法可以去官网看看文档,这边不做详细解释了 //KirbyComponent.vue**吸取数据动画 */function animateInhale() { const tlInhale = gsap.timeline() const delay = 0.1 const tlMouth = gsap.timeline() tlMouth .to(mouth.value, { duration: delay, scale: 0 }) .to(mouthOpen.value, { duration: 0.5, scale: 1 }) const kirbyFace = gsap.to(face.value, { delay, duration: 0.5, rotation: 5, y: -20 }) const kirbyArmLeft = gsap.to(armLeft.value, { delay, duration: 0.7, rotation: 80 }) const kirbyArmRight = gsap.to(armRight.value, { delay, duration: 0.7, rotation: -15 }) const kirbyLegRight = gsap.to(legRight.value, { delay: delay + 1, duration: 1, rotation: -15 }) tlInhale.add(tlMouth, 0) tlInhale.add(kirbyFace, 0) tlInhale.add(kirbyArmLeft, 0) tlInhale.add(kirbyArmRight, 0) tlInhale.add(kirbyLegRight, 0) return tlInhale}/**身体吸取数据动画 */function animatePuffed() { const tlPuffed = gsap.timeline() const tlMouth = gsap.timeline() tlMouth .to(mouthOpen.value, { duration: 0.2, scale: 0 }) .to(mouthFull.value, { duration: 0.1, scale: 1, y: { duration: 0, value: 0 } as any }) const kirbyFace = gsap.to(face.value, { duration: 0.5, rotate: 0, y: 0 }) const kirbyBody = gsap.to(body.value, { duration: 1.5, ease: 'elastic', scale: 1.1 }) const kirbyArmLeft = gsap.to(armLeft.value, { duration: 0.3, rotate: 0 }) const kirbyArmRight = gsap.to(armRight.value, { duration: 1.5, ease: 'elastic', rotation: 0, x: 10 }) const kirbyLegRight = gsap.to(legRight.value, { duration: 0.6, rotation: 0, y: 10 }) tlPuffed.add(tlMouth, 0) tlPuffed.add(kirbyFace, 0) tlPuffed.add(kirbyBody, 0) tlPuffed.add(kirbyArmLeft, 0) tlPuffed.add(kirbyArmRight, 0) tlPuffed.add(kirbyLegRight, 0) return tlPuffed}/**吞下数据动画 */function animateSwallow() { const tlSwallow = gsap.timeline() const tlEyes = gsap.timeline() const tlMouth = gsap.timeline() const dur = { duration: 0.8, ease: 'elastic' } tlEyes .to([eyeLeft.value, eyeRight.value], { duration: 0.1, scaleY: 0, y: 20 }) .to([eyeLeftClosed.value, eyeRightClosed.value], { duration: 0.1, scaleY: 1, y: 0 }) tlMouth .to(mouthFull.value, { duration: 0.1, scaleY: 0, y: 35 }) .to(mouthFrown.value, { duration: 0.1, scaleY: 1, y: { duration: 0, value: 0 } as any, onComplete: () = { starIsVisible.value = 2 } }) const kirbyCheeks = gsap.to(cheeks.value, { ...dur, y: 40 }) const kirbyBody = gsap.to(body.value, { ...dur, scale: 1 }) const bodyStroke = gsap.to(['#bodyStroke', '#bodyFill'], { ...dur, morphSVG: bodySwallow.value }) const kirbyArmLeft = gsap.to(armLeft.value, { ...dur, rotation: 70, y: 50 }) const kirbyArmRight = gsap.to(armRight.value, { ...dur, rotation: -50, x: 0, y: 50 }) tlSwallow.add(tlEyes, 0) tlSwallow.add(tlMouth, 0) tlSwallow.add(kirbyCheeks, 0) tlSwallow.add(kirbyBody, 0) tlSwallow.add(bodyStroke, 0) tlSwallow.add(kirbyArmLeft, 0) tlSwallow.add(kirbyArmRight, 0) return tlSwallow}/**邮件出现上升动画 */function animatePowerUp() { const tlPowerUp = gsap.timeline() const delay = 0.6 const tlEnvelope = gsap.timeline() animateReset() const kirbyLegRight = gsap.to(legRight.value, { delay: delay + 1, duration: 1, rotation: -15 }) const kirbyHat = gsap.to(hat.value, { onStart: () = { starPosition.value = { x: -10, y: -85 } starIsVisible.value = 6 }, startAt: { opacity: 1, scale: 0, y: 0 }, delay, duration: 0.6, ease: 'elastic', scale: 1 }) tlEnvelope .to(envelope.value, { startAt: { opacity: 1, scale: 0, y: 10 }, delay: delay + 0.3, duration: 0.6, ease: 'elastic', scale: 1 }) .to(envelopeStar.value, { startAt: { rotation: 500 }, duration: 1, rotation: 0, scale: 1 }) .to(envelope.value, { delay: 1, duration: 0.2, scaleY: 0.7 }) .to(envelope.value, { duration: 0.1, scaleY: 1, y: -5 }) .to(envelope.value, { duration: 1.5, ease: 'power2.inOut', opacity: 0, y: -110 }) .to(hat.value, { delay: 0.5, duration: 0.3, opacity: 0, scale: 0.7, y: -20 }) .to(legRight.value, { duration: 0.6, rotation: 0 }) tlPowerUp.add(kirbyLegRight, 0) tlPowerUp.add(kirbyHat, 0) tlPowerUp.add(tlEnvelope, 0) return tlPowerUp}/**重置动画 */function animateReset() { const tlEyes = gsap.timeline() const tlMouth = gsap.timeline() const dur = { delay: 0.2, duration: 1, ease: 'elastic' } tlEyes .to([eyeLeftClosed.value, eyeRightClosed.value], { delay: 0.2, duration: 0.1, scaleY: 0, y: -20 }) .to([eyeLeft.value, eyeRight.value], { duration: 0.2, scaleY: 1, y: { duration: 0.1, value: 0 } as any }) tlMouth .to(mouthFrown.value, { delay: 0.2, duration: 0.1, scaleY: 0, y: -35 }) .to(mouth.value, { duration: 0.1, scale: 1 }) gsap.to(cheeks.value, { ...dur, y: 0 }) gsap.to('#bodyStroke', { ...dur, morphSVG: bodyPathStroke.value }) gsap.to('#bodyFill', { ...dur, morphSVG: bodyPathFill.value }) gsap.to(armLeft.value, { ...dur, rotation: 0, y: 0 }) gsap.to(armRight.value, { ...dur, rotation: 0, y: 0 }) gsap.to(legRight.value, { y: 0 })} function animateStar(el: gsap.TweenTarget, done: () = void) { const tl = gsap.timeline({ onComplete: () = { starIsVisible.value = 0 done() } }) gsap.set(el, { scale: 0, transformOrigin: 'center center' }) if (starIsVisible.value 2) { gsap.set(el, { ...starPosition.value }) tl.to(el, { delay: 'random(0, 0.5)', duration: 'random(0.7, 1.1)', opacity: 0, rotation: 'random(360, 720)', scale: 'random(0.3, 0.6)', x: `random(${starPosition.value!.x - 60}, ${starPosition.value!.x + 60})`, y: `random(${starPosition.value!.y - 60}, ${starPosition.value!.y + 60})` }) } else { tl.to(el, { delay: 'random(0, 0.5)', duration: 0.7, rotation: 500, scale: 1, x: 'random(-120, 120)', y: 'random(-120, -50)' }).to(el, { duration: 1, ease: 'elastic', opacity: 0, scale: 2 }) }}/**吐出星星动画 */function animateSpit() { const tlSpit = gsap.timeline() const tlMouth = gsap.timeline() tlMouth .to(mouthFull.value, { duration: 0.05, scaleY: 0 }) .to(mouthOpen.value, { duration: 0.4, scale: 0.3 }) .to(star.value, { startAt: { opacity: 1, rotation: 0, scale: 0, transformOrigin: 'center center', x: -40, y: -20 }, delay: -0.2, duration: 1, ease: 'power2.inOut', rotation: 720, scale: 1, x: -350 }) .to(star.value, { duration: 0.2, scaleX: 0.9, transformOrigin: 'left center' }) .to(star.value, { onStart: () = { starPosition.value = { x: -350, y: -20 } starIsVisible.value = 6 }, duration: 0.2, opacity: 0, scaleX: 1, transformOrigin: 'left center' }) .to( mouthOpen.value, { duration: 0.1, scale: 0 }, '-=1' ) .to( mouth.value, { duration: 0.1, scale: 1 }, '-=0.9' ) const kirbyBody = gsap.to(body.value, { duration: 0.2, scale: 1 }) const kirbyArmRight = gsap.to(armRight.value, { duration: 0.7, x: 0 }) const kirbyLegRight = gsap.to(legRight.value, { duration: 0.6, rotation: 0, y: 0 }) tlSpit.add(tlMouth, 0) tlSpit.add(kirbyBody, 0) tlSpit.add(kirbyArmRight, 0) tlSpit.add(kirbyLegRight, 0) return tlSpit}/**添加样式方法,style中配置了就可以不执行这个方法 */function styleInject(css: string, ref?: { insertAt?: any } | undefined) { if (ref === void 0) ref = {} const insertAt = ref.insertAt if (!css || typeof document === 'undefined') { return } const head = document.head || document.getElementsByTagName('head')[0] const style = document.createElement('style') as any style.type = 'text/css' if (insertAt === 'top') { if (head.firstChild) { head.insertBefore(style, head.firstChild) } else { head.appendChild(style) } } else { head.appendChild(style) } if (style.styleSheet) { style.styleSheet.cssText = css } else { style.appendChild(document.createTextNode(css)) }}四、组件初始化以及对是否符合正则的对应动画执行方法在onMounted钩子里面初始化星之卡比的外观 //KirbyComponent.vueonMounted(() = { bodyPathFill.value = MorphSVGPlugin.convertToPath(bodyFill.value) as any bodyPathStroke.value = MorphSVGPlugin.convertToPath(bodyStroke.value) as any gsap.set(mouthOpen.value, { transformOrigin: '17px 26px', scale: 0 }) gsap.set(mouthFull.value, { transformOrigin: 'center center', scale: 0 }) gsap.set(mouthFrown.value, { transformOrigin: 'center center', scaleY: 0 }) gsap.set(eyeLeft.value, { transformOrigin: 'center 17px' }) gsap.set(eyeRight.value, { transformOrigin: 'center 17px' }) gsap.set(eyeLeftClosed.value, { transformOrigin: 'center bottom', scaleY: 0 }) gsap.set(eyeRightClosed.value, { transformOrigin: 'center bottom', scaleY: 0 }) gsap.set(face.value, { transformOrigin: '25px bottom' }) gsap.set(armLeft.value, { transformOrigin: '70px 42px' }) gsap.set(armRight.value, { transformOrigin: '-35px 44px' }) gsap.set(legRight.value, { transformOrigin: '-13px -6px' }) gsap.set(body.value, { transformOrigin: 'left bottom' }) gsap.set(bodyStroke.value, { transformOrigin: 'center bottom' }) gsap.set(bodyFill.value, { transformOrigin: 'center bottom' }) gsap.set(hat.value, { scale: 0, transformOrigin: 'center bottom' }) gsap.set(envelope.value, { scale: 0, transformOrigin: 'center bottom', y: 10 }) gsap.set(envelopeStar.value, { scale: 0, transformOrigin: 'center center' }) gsap.set(star.value, { scale: 0, transformOrigin: 'center center' })})监听props中的值,根据值去动态执行相应的动画 //设置接收props的值const props = defineProps({ swallow: Boolean, spit: Boolean}) //动画结束向父组件传递结束信息const emits = defineEmits(['animationdone']) //监听props中的值,去执行对应方法watch(() = props.swallow, (newValue) = { if (newValue) { aswallow() }}) watch(() = props.spit, (newValue) = { if (newValue) { aspit() }}) //正则匹配为true时的动画function aswallow(): any { if (props.swallow) { animateInhale() .then(animatePuffed as any) .then(animateSwallow) .then(animatePowerUp) .then(() = { emits('animationdone') }) }} //正则匹配为false时的动画function aspit(): any { if (props.spit) { console.log(2) animateInhale() .then(animatePuffed) .then(animateSpit) .then(() = { emits('animationdone') }) }}五、输入框外观//KirbyView.vuetemplate main class="container" div class="input-wrap" svg class="cloudy" viewBox="0 0 277 145" path class="cloudy-poofs" d="M218,20c-0.74,0-1.47,0.03-2.2,0.06C204.99,7.77,189.16,0,171.5,0c-12.22,0-23.58,3.72-33,10.09 C129.08,3.72,117.72,0,105.5,0C87.84,0,72.01,7.77,61.2,20.06C60.47,20.03,59.74,20,59,20C26.42,20,0,46.42,0,79 c0,32.58,26.42,59,59,59c5.26,0,10.36-0.7,15.21-1.99C83.28,141.7,94,145,105.5,145c12.22,0,23.58-3.72,33-10.09 c9.42,6.37,20.78,10.09,33,10.09c11.5,0,22.22-3.3,31.29-8.99c4.85,1.29,9.95,1.99,15.21,1.99c32.58,0,59-26.42,59-59 C277,46.42,250.58,20,218,20z" / /svg div v-if="emailText" class="input-text" span v-for="(letter, index) in emailText" :key="index" class="input-text-letter" {{ letter }}/span /div div :class="{ disabled: emailIsDisabled }" class="dreamy-input" label class="dreamy-input-label" for="dreamyInput" Email/label input id="dreamyInput" v-model="emailValue" :aria-describedby="isNotValid ? 'dreamyInputErr' : undefined" :disabled="emailIsDisabled" autocomplete="off" class="dreamy-input-control" type="email" @keydown.enter="submitMail" transition name="fade" span v-if="isNotValid" id="dreamyInputErr" class="dreamy-input-error" Please enter a valid email. /span /transition button aria-label="Subscribe" class="dreamy-input-button" @click="submitMail" svg class="dreamy-input-button-svg" viewBox="0 0 35 35" path class="dreamy-input-button-star" d="M29.36,23.14l4.63-5.32c2.08-2.39,0.77-6.08-2.39-6.7l-5.23-1.02c-0.23-0.05-0.44-0.19-0.56-0.39l-4.67-7.7 c-1.64-2.69-5.64-2.69-7.27,0l-4.67,7.7c-0.12,0.2-0.33,0.34-0.56,0.39L3.4,11.13c-3.16,0.62-4.47,4.31-2.39,6.7l4.63,5.32 c0.17,0.2,0.24,0.47,0.18,0.73L4.31,29.9c-0.83,3.32,2.61,6.12,5.82,4.74l7.03-3.03c0.22-0.09,0.47-0.09,0.68,0l7.03,3.03 c3.21,1.38,6.65-1.42,5.82-4.74l-1.51-6.04C29.12,23.61,29.18,23.34,29.36,23.14z" / /svg /button /div /div Kirby :swallow="isValid" :spit="isNotValid" @animationdone="reset" / /main/template style lang="scss"$black: #87213c;$white: #fff; $primary-color: #f9dde3;$primary-color-tint: #fcf2f5;$secondary-color: #ff2961;$accent-color: #fae798; $mail-color: #326dcc;$star-sparkly-color: #b8d7ff; $primary-font: 'Fredoka One', sans-serif; :root { --black: #{$black}; --white: #{$white}; --primary-color: #{$primary-color}; --primary-color-tint: #{$primary-color-tint}; --secondary-color: #{$secondary-color}; --accent-color: #{$accent-color}; --mail-color: #{$mail-color}; --star-color: #{$accent-color}; --star-sparkly-color: #{$star-sparkly-color};} @import url('https://fonts.googleapis.com/css2?family=Fredoka+One&display=swap'); * { box-sizing: border-box; } body { display: grid; place-items: center; min-height: 100vh; background-color: var(--primary-color-tint); font-size: 16px; line-height: 1; font-family: $primary-font;} .container { display: grid; grid-template-columns: 2fr 1fr; align-items: center; width: 400px; max-width: 400px;} .input-text { position: absolute; z-index: 5; width: 17rem; font-size: 1.25rem; line-height: 1.5; color: var(--black); transform: translate(1rem, 0.625rem); overflow: hidden; &-letter { display: inline-block; transform-origin: center bottom; }} .input-wrap { position: relative;} .cloudy { position: absolute; top: 50%; left: 50%; width: 150%; transform: translate(-50%, -50%); &-poofs { fill: var(--white); }} .fade { &-enter-active, &-leave-active { transition: 0.3s; } &-enter-from, &-leave-to { opacity: 0; }} .dreamy-input { --offset-top: 0.1875rem; --offset-left: -0.125rem; --control-height: 3.125rem; position: relative; &::after { content: ''; position: absolute; top: var(--offset-top); left: var(--offset-left); height: 100%; width: 100%; border: 3px solid var(--black); pointer-events: none; transition: border 0.5s ease-in-out; } &::after, &-control { border-radius: var(--control-height); } &-control { width: 15.625rem; height: var(--control-height); padding: 0 calc(1rem + var(--control-height)) 0 1rem; border: 0; background-color: var(--primary-color); font-family: $primary-font; font-size: 1.25rem; color: var(--black); caret-color: var(--secondary-color); transition: background-color 0.5s ease-in-out; &:focus { outline: none; } &:disabled { background-color: var(--primary-color-tint); } } &:focus-within { &::after { border-color: var(--secondary-color); } } &-label, &-error { position: absolute; left: 1rem; font-size: 0.75rem; letter-spacing: 0.05em; } &-label { top: -0.75rem; color: var(--black); } &-error { bottom: -1.5rem; color: var(--secondary-color); } &-button { display: grid; place-items: center; position: absolute; top: calc(var(--offset-top) + 3px); right: 0; height: var(--control-height); width: var(--control-height); padding: 0; border: 0; background-color: transparent; color: var(--white); cursor: pointer; transition: 0.4s ease-in-out; &-star { fill: currentColor; stroke: var(--star-stroke, transparent); stroke-width: 3px; } &-svg { width: 40%; overflow: visible; } &:focus { outline: none; color: var(--secondary-color); } &:hover { --star-stroke: var(--black); transform: scale(1.2); color: var(--accent-color); } } &.disabled { &::after { border-color: var(--primary-color); } .dreamy-input-button { color: var(--primary-color); } }}/style style lang="scss" scoped.kirby { position: relative; z-index: 5; display: inline-block; height: 120px; width: 120px; overflow: visible;}.kirby-stroke { stroke: var(--black); stroke-width: 3px; stroke-miterlimit: 10; stroke-linecap: round; stroke-linejoin: round; fill: none;}.kirby-body { fill: var(--primary-color);}.kirby-mouth-open-tongue,.kirby-foot { fill: var(--secondary-color);}.kirby-hat-dark,.kirby-mouth-open-inner,.kirby-eye { fill: var(--black);}.kirby-blush { stroke: var(--secondary-color);}.kirby-blush-small { stroke-width: 2.5px;}.kirby-star { stroke-width: 3px; stroke: currentColor; fill: var(--star-color);}.kirby-star-splashy { color: transparent;}.kirby-star-spit { color: var(--black);}.kirby-hat-band { fill: var(--accent-color);}.kirby-hat { fill: var(--mail-color);}.kirby-envelope { fill: var(--white);}.kirby-envelope-star { fill: var(--primary-color);}/style六、配置输入框节点以及输入框文字移动动画//KirbyView.vueimport { ref,nextTick } from 'vue';import Kirby from '@/components/kirby/KirbyComponent.vue';// import Kirby from '../components/kirby/kirby';//引入gasp插件import { gsap } from 'gsap'//配置输入框节点const emailValue = ref('');const emailIsDisabled = ref(false);const emailText = ref('');const isValid = ref(false);const isNotValid = ref(false);//输入框判断方法function submitMail() { if (emailValue.value) { const emailRegex = /^(([^()[\]\\.,;:\s@"]+(\.[^()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; isValid.value = emailRegex.test(emailValue.value); isNotValid.value = !isValid.value; emailText.value = emailValue.value.length 15 ? emailValue.value.slice(0, 15) + '...' : emailValue.value; emailValue.value = ''; emailIsDisabled.value = true; nextTick(animateText); } } //输入框文字吸入效果function animateText() { const tlJiggle = gsap.timeline({ delay: 1, defaults: { duration: 0.1 }}); tlJiggle .to('.input-text-letter', { y: 'random(-6, 4)' }) .to('.input-text-letter', { y: 'random(-6, 4)' }) .to('.input-text-letter', { y: 'random(-6, 4)' }) .to('.input-text-letter', { y: 'random(-6, 4)' }) .to('.input-text-letter', { y: 0, duration: 0.1 }) .to('.input-text-letter', { delay: 0, duration: 0.5, ease: 'expo.in', x: 300, stagger: { amount: 0.3, from: 'end' } });}//重置动画flagfunction reset() { emailIsDisabled.value = false; isValid.value = false; isNotValid.value = false; emailText.value = '';}代码地址gitee:https://gitee.com/liu_soon/vue3.3 npm install npm run dev 运行成功之后打开:http://localhost:5173/Kirby 结语这个主要是学习gasp动画的,了解这篇文章能够较快的入门gasp这个技术。最主要的是这个输入框好看,而且适合用在博客上。 点击关注公众号,“技术干货”及时达! 阅读原文

上一篇:2018-12-15_Facebook开源NLP建模框架PyText,从论文到产品部署只需数天 下一篇:2024-12-05_Claude 小说 | 我终于写出了那串完美提示词

TAG标签:

15
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价