Expressive Cartoon Face: Animasi SVG Karakter Berambut dengan Ekspresi Interaktif

DengarkanDibaca Otomatis

Expressive Cartoon Face adalah ilustrasi vektor animasi yang dikembangkan dari SVG wajah karakter kartun bergaris hitam. Referensi menampilkan kepala membulat, rambut tersisir ke samping, telinga simetris, mata bundar, alis ekspresif, dan mulut melengkung.

Rekonstruksi mempertahankan proporsi dasar tersebut melalui pendekatan Premium Vector Illustration, didukung Cartoon Character Rigging dan Soft 3D Illustration. Warna kulit hangat, rambut cokelat berlapis, pencahayaan lembut, serta detail mata memberikan dimensi visual tanpa menghilangkan karakter sederhana dari desain aslinya.

Keunggulan utama artwork terletak pada artikulasi wajah. Mata dapat berkedip, pupil mengikuti posisi pointer, alis bergerak, mulut berubah ekspresi, dan kepala memiliki gerakan mikro. Rambut serta telinga juga memiliki animasi sekunder yang mengikuti karakter gerakan kepala.

Parameter Implementasi Fungsi
Objek Utama Wajah karakter kartun berambut Mempertahankan identitas SVG input
Gaya Artistik Premium Vector Illustration, Cartoon Character Rigging, Soft 3D Illustration Menghasilkan karakter ekspresif berlapis
Geometri SVG Path Bézier, ellipse, circle, dan hierarchical groups Membentuk wajah dan anatomi ekspresi
Material Visual Linear gradient, radial gradient, local highlights Menciptakan volume semu dan pencahayaan
Animasi Utama Kedipan, perubahan senyum, dan gerakan kepala Memberikan ekspresi karakter
Artikulasi Internal Mata, pupil, alis, mulut, rambut, telinga Mendukung gerakan independen
Matematika dan Fisika Interpolasi eksponensial berbasis delta time Menghaluskan respons pointer
JavaScript Pointer Events dan requestAnimationFrame Mengendalikan arah pandangan secara dinamis
Responsivitas ViewBox dan CSS min() Menyesuaikan ukuran artwork
Aksesibilitas Accessible name, reduced motion, visibility handling Mengurangi animasi sesuai preferensi pengguna

Rekayasa SVG, Animasi, dan Source Code Lengkap

Artwork menggunakan koordinat SVG 260 × 270, dengan geometri wajah disusun mengikuti referensi 211 × 211. Lapisan telinga berada di belakang kepala, sementara rambut depan menutupi bagian dahi. Mata, alis, dan mulut menggunakan grup terpisah agar dapat dianimasikan tanpa mengubah siluet utama.

Pencahayaan dibangun menggunakan gradient SVG. Warna kulit memiliki area terang di bagian atas dan bayangan di sekitar rahang. Rambut menggunakan beberapa lapisan kurva untuk menghasilkan kesan helaian tersisir.

Animasi deklaratif menggunakan CSS keyframes. JavaScript hanya menangani respons arah pandangan terhadap pointer. Pergerakan pupil dihitung menggunakan interpolasi eksponensial dengan faktor 1 - Math.exp(-k * dt), sehingga kecepatan penyesuaian tidak bergantung langsung pada jumlah frame.

Ketika pointer meninggalkan artwork, pandangan kembali ke tengah. Animasi JavaScript dihentikan ketika dokumen tidak terlihat atau pengguna memilih reduced motion. Interaksi pointer bersifat tambahan; ilustrasi tetap dapat dinikmati tanpa interaksi.

Simpan source code berikut sebagai expressive-cartoon-face.html. Dokumen dapat dibuka langsung di browser tanpa dependency eksternal. Background transparan dan tidak terdapat elemen antarmuka dekoratif.

<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Expressive Cartoon Face — SVG Motion Art</title>

<style>
:root {
  color-scheme: light dark;
}

html,
body {
  margin: 0;
  width: 100%;
  min-height: 100%;
}

body {
  min-height: 100vh;
  min-height: 100dvh;
  display: grid;
  place-items: center;
  background: transparent;
  overflow: hidden;
}

.ecf-artwork {
  width: min(92vw, 90vh, 620px);
  width: min(92vw, 90dvh, 620px);
  height: auto;
  display: block;
  overflow: visible;
  touch-action: pan-y;
}

.ecf-head {
  transform-origin: 130px 135px;
  animation: ecf-head-idle 5s ease-in-out infinite;
}

.ecf-hair-top {
  transform-origin: 105px 55px;
  animation: ecf-hair-motion 5s ease-in-out infinite;
}

.ecf-ear-left {
  transform-origin: 51px 131px;
  animation: ecf-ear-motion 5s ease-in-out infinite;
}

.ecf-ear-right {
  transform-origin: 208px 131px;
  animation: ecf-ear-motion 5s ease-in-out
    infinite reverse;
}

.ecf-eyelid-left,
.ecf-eyelid-right {
  transform-box: fill-box;
  transform-origin: center;
  animation: ecf-blink 5.7s ease-in-out infinite;
}

.ecf-eyelid-right {
  animation-delay: .07s;
}

.ecf-brow-left {
  transform-origin: 105px 98px;
  animation: ecf-brow-motion 5s ease-in-out infinite;
}

.ecf-brow-right {
  transform-origin: 158px 98px;
  animation: ecf-brow-motion 5s ease-in-out
    infinite reverse;
}

.ecf-mouth {
  transform-origin: 132px 187px;
  animation: ecf-smile-motion 5s ease-in-out infinite;
}

.ecf-cheek {
  animation: ecf-cheek-glow 5s ease-in-out infinite;
}

.ecf-pupil-left,
.ecf-pupil-right {
  will-change: transform;
}

@keyframes ecf-head-idle {
  0%, 100% {
    transform: translateY(0) rotate(0deg);
  }
  25% {
    transform: translateY(-2px) rotate(-1deg);
  }
  55% {
    transform: translateY(1px) rotate(1.3deg);
  }
  78% {
    transform: translateY(-1px) rotate(-.5deg);
  }
}

@keyframes ecf-hair-motion {
  0%, 100% {
    transform: rotate(0deg);
  }
  35% {
    transform: rotate(-1.5deg);
  }
  70% {
    transform: rotate(1deg);
  }
}

@keyframes ecf-ear-motion {
  0%, 100% {
    transform: rotate(0deg);
  }
  50% {
    transform: rotate(2deg);
  }
}

@keyframes ecf-blink {
  0%, 42%, 46%, 74%, 78%, 100% {
    transform: scaleY(0);
  }
  44%, 76% {
    transform: scaleY(1);
  }
}

@keyframes ecf-brow-motion {
  0%, 100% {
    transform: translateY(0);
  }
  45% {
    transform: translateY(-3px);
  }
  70% {
    transform: translateY(1px);
  }
}

@keyframes ecf-smile-motion {
  0%, 100% {
    transform: scaleY(1);
  }
  45% {
    transform: scaleY(1.25);
  }
  70% {
    transform: scaleY(.85);
  }
}

@keyframes ecf-cheek-glow {
  0%, 100% {
    opacity: .32;
  }
  50% {
    opacity: .57;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ecf-artwork *,
  .ecf-artwork {
    animation: none !important;
    transition: none !important;
  }

  .ecf-eyelid-left,
  .ecf-eyelid-right {
    transform: scaleY(0);
  }

  .ecf-pupil-left,
  .ecf-pupil-right {
    will-change: auto;
  }
}
</style>
</head>

<body>

<svg
  class="ecf-artwork"
  xmlns="http://www.w3.org/2000/svg"
  viewBox="0 0 260 270"
  role="img"
  aria-labelledby="ecf-title ecf-desc"
>

<title id="ecf-title">
Expressive Cartoon Face
</title>

<desc id="ecf-desc">
Wajah karakter kartun dengan rambut cokelat
tersisir, mata besar, alis ekspresif, telinga,
dan senyum. Mata mengikuti gerakan pointer
ketika animasi diaktifkan.
</desc>

<defs>

  <linearGradient
    id="ecf-skin"
    x1="0" y1="0"
    x2=".8" y2="1"
    objectBoundingBox="true"
  >
    <stop offset="0%" stop-color="#FFE3B8"/>
    <stop offset="40%" stop-color="#F7BF91"/>
    <stop offset="75%" stop-color="#ECA578"/>
    <stop offset="100%" stop-color="#D98966"/>
  </linearGradient>

  <linearGradient
    id="ecf-ear-skin"
    x1="0" y1="0"
    x2="1" y2="1"
    objectBoundingBox="true"
  >
    <stop offset="0%" stop-color="#FAD1A1"/>
    <stop offset="100%" stop-color="#D98C6F"/>
  </linearGradient>

  <linearGradient
    id="ecf-hair"
    x1="0" y1="0"
    x2=".7" y2="1"
    objectBoundingBox="true"
  >
    <stop offset="0%" stop-color="#A46A48"/>
    <stop offset="28%" stop-color="#75472F"/>
    <stop offset="70%" stop-color="#4F3028"/>
    <stop offset="100%" stop-color="#38251F"/>
  </linearGradient>

  <linearGradient
    id="ecf-hair-highlight"
    x1="0" y1="0"
    x2="1" y2="1"
    objectBoundingBox="true"
  >
    <stop offset="0%" stop-color="#D59A67"/>
    <stop offset="100%" stop-color="#815039"/>
  </linearGradient>

  <radialGradient id="ecf-cheek-gradient">
    <stop offset="0%" stop-color="#E97875"
      stop-opacity=".65"/>
    <stop offset="100%" stop-color="#E97875"
      stop-opacity="0"/>
  </radialGradient>

  <radialGradient id="ecf-eye-white">
    <stop offset="0%" stop-color="#FFFFFF"/>
    <stop offset="100%" stop-color="#E8F0F1"/>
  </radialGradient>

  <radialGradient id="ecf-iris">
    <stop offset="0%" stop-color="#33291F"/>
    <stop offset="45%" stop-color="#775036"/>
    <stop offset="100%" stop-color="#A97848"/>
  </radialGradient>

  <linearGradient
    id="ecf-lip"
    x1="0" y1="0"
    x2="0" y2="1"
    objectBoundingBox="true"
  >
    <stop offset="0%" stop-color="#A6504C"/>
    <stop offset="100%" stop-color="#763C3A"/>
  </linearGradient>

  <clipPath id="ecf-left-eye-clip">
    <ellipse cx="105" cy="133" rx="17" ry="19"/>
  </clipPath>

  <clipPath id="ecf-right-eye-clip">
    <ellipse cx="157" cy="133" rx="17" ry="19"/>
  </clipPath>

</defs>

<g class="ecf-head">

  <g class="ecf-ear-left">

    <path
      d="
      M58 116
      C43 107 29 116 28 132
      C26 151 37 163 55 162
      C65 161 69 150 68 137
      Z"
      fill="url(#ecf-ear-skin)"
      stroke="#A96B55"
      stroke-width="2.5"
    />

    <path
      d="
      M52 126
      C41 119 36 129 39 140
      C42 149 49 153 56 150"
      fill="none"
      stroke="#BA786C"
      stroke-width="4"
      stroke-linecap="round"
    />

    <path
      d="M37 132 Q40 123 48 125"
      fill="none"
      stroke="#FFE5C3"
      stroke-width="3"
      stroke-linecap="round"
      opacity=".65"
    />

  </g>

  <g class="ecf-ear-right">

    <path
      d="
      M202 116
      C217 107 231 116 232 132
      C234 151 223 163 205 162
      C195 161 191 150 192 137
      Z"
      fill="url(#ecf-ear-skin)"
      stroke="#A96B55"
      stroke-width="2.5"
    />

    <path
      d="
      M208 126
      C219 119 224 129 221 140
      C218 149 211 153 204 150"
      fill="none"
      stroke="#BA786C"
      stroke-width="4"
      stroke-linecap="round"
    />

    <path
      d="M223 132 Q220 123 212 125"
      fill="none"
      stroke="#FFE5C3"
      stroke-width="3"
      stroke-linecap="round"
      opacity=".65"
    />

  </g>

  <path
    d="
    M53 78
    C53 42 82 22 127 21
    C174 19 207 43 207 82
    L207 161
    C207 209 173 243 130 243
    C87 243 53 209 53 161
    Z"
    fill="url(#ecf-skin)"
    stroke="#B77B60"
    stroke-width="2.8"
  />

  <path
    d="
    M68 101
    C62 143 66 190 88 216
    C100 231 116 237 131 237"
    fill="none"
    stroke="#FFF0D3"
    stroke-width="7"
    stroke-linecap="round"
    opacity=".36"
  />

  <path
    d="
    M193 111
    C202 151 192 204 165 225"
    fill="none"
    stroke="#B66E58"
    stroke-width="9"
    stroke-linecap="round"
    opacity=".14"
  />

  <ellipse
    class="ecf-cheek"
    cx="82"
    cy="167"
    rx="31"
    ry="23"
    fill="url(#ecf-cheek-gradient)"
  />

  <ellipse
    class="ecf-cheek"
    cx="180"
    cy="167"
    rx="31"
    ry="23"
    fill="url(#ecf-cheek-gradient)"
  />

  <path
    d="
    M122 145
    C119 155 115 163 118 169
    C122 174 135 175 142 169
    C145 166 142 161 139 157"
    fill="#E7A078"
    stroke="#C8896B"
    stroke-width="1.5"
  />

  <path
    d="
    M121 165
    Q129 169 138 165"
    fill="none"
    stroke="#FFE0B2"
    stroke-width="2.5"
    stroke-linecap="round"
    opacity=".8"
  />

  <g class="ecf-eye-left">

    <ellipse
      cx="105"
      cy="133"
      rx="18"
      ry="20"
      fill="#704B3C"
      opacity=".2"
    />

    <ellipse
      cx="105"
      cy="133"
      rx="17"
      ry="19"
      fill="url(#ecf-eye-white)"
      stroke="#774D42"
      stroke-width="2"
    />

    <g clip-path="url(#ecf-left-eye-clip)">

      <g class="ecf-pupil-left">

        <circle
          cx="105"
          cy="134"
          r="10.5"
          fill="url(#ecf-iris)"
        />

        <circle
          cx="105"
          cy="134"
          r="5.5"
          fill="#231C1B"
        />

        <circle
          cx="101"
          cy="129"
          r="4"
          fill="#FFFFFF"
        />

        <circle
          cx="109"
          cy="138"
          r="1.7"
          fill="#FFFFFF"
          opacity=".75"
        />

      </g>

    </g>

    <ellipse
      class="ecf-eyelid-left"
      cx="105"
      cy="133"
      rx="17"
      ry="19"
      fill="#E9AA81"
      stroke="#9D6654"
      stroke-width="2"
    />

  </g>

  <g class="ecf-eye-right">

    <ellipse
      cx="157"
      cy="133"
      rx="18"
      ry="20"
      fill="#704B3C"
      opacity=".2"
    />

    <ellipse
      cx="157"
      cy="133"
      rx="17"
      ry="19"
      fill="url(#ecf-eye-white)"
      stroke="#774D42"
      stroke-width="2"
    />

    <g clip-path="url(#ecf-right-eye-clip)">

      <g class="ecf-pupil-right">

        <circle
          cx="157"
          cy="134"
          r="10.5"
          fill="url(#ecf-iris)"
        />

        <circle
          cx="157"
          cy="134"
          r="5.5"
          fill="#231C1B"
        />

        <circle
          cx="153"
          cy="129"
          r="4"
          fill="#FFFFFF"
        />

        <circle
          cx="161"
          cy="138"
          r="1.7"
          fill="#FFFFFF"
          opacity=".75"
        />

      </g>

    </g>

    <ellipse
      class="ecf-eyelid-right"
      cx="157"
      cy="133"
      rx="17"
      ry="19"
      fill="#E9AA81"
      stroke="#9D6654"
      stroke-width="2"
    />

  </g>

  <g class="ecf-brow-left">

    <path
      d="
      M86 103
      Q102 96 119 105"
      fill="none"
      stroke="#513329"
      stroke-width="7"
      stroke-linecap="round"
    />

    <path
      d="
      M89 101
      Q102 97 115 103"
      fill="none"
      stroke="#A06B49"
      stroke-width="2"
      stroke-linecap="round"
    />

  </g>

  <g class="ecf-brow-right">

    <path
      d="
      M143 105
      Q160 96 177 103"
      fill="none"
      stroke="#513329"
      stroke-width="7"
      stroke-linecap="round"
    />

    <path
      d="
      M147 103
      Q161 98 174 101"
      fill="none"
      stroke="#A06B49"
      stroke-width="2"
      stroke-linecap="round"
    />

  </g>

  <g class="ecf-mouth">

    <path
      d="
      M93 187
      C109 199 150 201 169 185
      C159 204 143 214 130 214
      C115 214 101 204 93 187
      Z"
      fill="url(#ecf-lip)"
      stroke="#934C45"
      stroke-width="1.5"
    />

    <path
      d="
      M99 190
      Q130 203 162 189
      Q155 199 130 200
      Q110 200 99 190
      Z"
      fill="#FFF7E9"
    />

    <path
      d="
      M113 207
      Q130 201 148 208
      Q130 216 113 207
      Z"
      fill="#DD827D"
      opacity=".8"
    />

    <path
      d="
      M91 185
      Q95 190 100 190
      M164 189
      Q169 187 171 183"
      fill="none"
      stroke="#A75D55"
      stroke-width="2"
      stroke-linecap="round"
    />

  </g>

  <g class="ecf-hair-top">

    <path
      d="
      M53 109
      C51 88 49 65 57 47
      C69 22 97 12 127 11
      C162 10 195 23 205 48
      C215 71 209 100 207 119
      L199 115
      C198 91 195 79 186 72
      C170 58 146 58 127 61
      C110 64 89 66 73 60
      C66 69 62 88 61 110
      Z"
      fill="url(#ecf-hair)"
      stroke="#392820"
      stroke-width="3"
    />

    <path
      d="
      M57 47
      C74 25 105 20 135 23
      C165 25 188 37 198 55
      C180 47 161 44 140 48
      C111 54 83 62 57 47
      Z"
      fill="url(#ecf-hair-highlight)"
      opacity=".9"
    />

    <path
      d="
      M61 40
      C78 57 104 57 131 51
      C158 45 181 50 199 66"
      fill="none"
      stroke="#DCA170"
      stroke-width="4"
      stroke-linecap="round"
      opacity=".55"
    />

    <path
      d="
      M66 31
      C89 45 111 46 137 39
      M77 24
      C106 34 129 33 154 30
      M105 18
      C138 20 171 29 189 43"
      fill="none"
      stroke="#D29A68"
      stroke-width="2.5"
      stroke-linecap="round"
      opacity=".48"
    />

    <path
      d="
      M57 62
      Q69 70 76 67
      M58 75
      Q67 81 71 79
      M199 72
      Q204 83 204 96"
      fill="none"
      stroke="#B47C55"
      stroke-width="3"
      stroke-linecap="round"
      opacity=".5"
    />

    <path
      d="
      M57 47
      C68 63 83 66 102 64
      C124 62 143 54 165 57
      C182 58 194 67 199 83
      C190 68 173 64 154 66
      C126 69 105 75 82 70
      C68 67 60 59 57 47
      Z"
      fill="#482D26"
      opacity=".68"
    />

    <path
      d="
      M62 49
      C83 62 106 61 130 55"
      fill="none"
      stroke="#E1AD79"
      stroke-width="2"
      stroke-linecap="round"
      opacity=".6"
    />

  </g>

  <path
    d="
    M64 111
    Q64 94 70 80
    M198 113
    Q198 96 193 85"
    fill="none"
    stroke="#6A4030"
    stroke-width="4"
    stroke-linecap="round"
    opacity=".45"
  />

  <path
    d="
    M103 229
    Q130 240 157 228"
    fill="none"
    stroke="#FFE5BE"
    stroke-width="3"
    stroke-linecap="round"
    opacity=".52"
  />

</g>

</svg>

<script>
(() => {
  'use strict';

  const svg = document.querySelector('.ecf-artwork');
  const left = svg.querySelector('.ecf-pupil-left');
  const right = svg.querySelector('.ecf-pupil-right');

  const motionQuery = window.matchMedia(
    '(prefers-reduced-motion: reduce)'
  );

  const state = {
    x: 0,
    y: 0,
    targetX: 0,
    targetY: 0,
    lastTime: 0,
    frame: null
  };

  const maxOffset = 4.2;
  const smoothing = 12;

  function clamp(value, min, max) {
    return Math.max(min, Math.min(max, value));
  }

  function updateEyes() {
    const transform =
      `translate(${state.x.toFixed(3)} ` +
      `${state.y.toFixed(3)})`;

    left.setAttribute('transform', transform);
    right.setAttribute('transform', transform);
  }

  function resetEyes() {
    state.x = 0;
    state.y = 0;
    state.targetX = 0;
    state.targetY = 0;
    state.lastTime = 0;
    updateEyes();
  }

  function stop() {
    if (state.frame !== null) {
      cancelAnimationFrame(state.frame);
      state.frame = null;
    }

    state.lastTime = 0;
  }

  function animate(timestamp) {
    state.frame = null;

    if (document.hidden || motionQuery.matches) {
      return;
    }

    const dt = state.lastTime
      ? Math.min(
          (timestamp - state.lastTime) / 1000,
          0.05
        )
      : 1 / 60;

    state.lastTime = timestamp;

    const alpha = 1 - Math.exp(-smoothing * dt);

    state.x += (state.targetX - state.x) * alpha;
    state.y += (state.targetY - state.y) * alpha;

    updateEyes();

    const moving =
      Math.abs(state.targetX - state.x) > 0.01 ||
      Math.abs(state.targetY - state.y) > 0.01;

    if (moving) {
      state.frame = requestAnimationFrame(animate);
    } else {
      state.x = state.targetX;
      state.y = state.targetY;
      updateEyes();
      state.lastTime = 0;
    }
  }

  function start() {
    if (
      state.frame !== null ||
      document.hidden ||
      motionQuery.matches
    ) {
      return;
    }

    state.frame = requestAnimationFrame(animate);
  }

  function handlePointer(event) {
    if (motionQuery.matches) {
      return;
    }

    const rect = svg.getBoundingClientRect();

    if (!rect.width || !rect.height) {
      return;
    }

    const centerX = rect.left + rect.width / 2;
    const centerY = rect.top + rect.height / 2;

    const dx = (event.clientX - centerX) /
      (rect.width / 2);

    const dy = (event.clientY - centerY) /
      (rect.height / 2);

    state.targetX = clamp(
      dx * maxOffset,
      -maxOffset,
      maxOffset
    );

    state.targetY = clamp(
      dy * maxOffset,
      -maxOffset,
      maxOffset
    );

    start();
  }

  function handlePointerLeave() {
    state.targetX = 0;
    state.targetY = 0;
    start();
  }

  function handleVisibility() {
    if (document.hidden) {
      stop();
    } else if (!motionQuery.matches) {
      start();
    }
  }

  function handleMotionChange() {
    stop();
    resetEyes();
  }

  svg.addEventListener('pointermove', handlePointer);

  svg.addEventListener(
    'pointerleave',
    handlePointerLeave
  );

  document.addEventListener(
    'visibilitychange',
    handleVisibility
  );

  motionQuery.addEventListener(
    'change',
    handleMotionChange
  );

  window.addEventListener('pagehide', stop);

  resetEyes();
})();
</script>

</body>
</html>
Sosial Media

Dari ilustrasi SVG hitam putih menjadi karakter kartun yang bisa berekspresi!

Expressive Cartoon Face adalah eksperimen SVG Motion Art yang mengembangkan wajah karakter sederhana menjadi ilustrasi vektor dengan material lembut, rambut cokelat berlapis, mata bercahaya, dan ekspresi yang terasa hidup.

Detail menariknya bukan sekadar perubahan warna. Mata karakter dapat mengikuti gerakan pointer, kelopak mata berkedip, alis bergerak, senyum berubah secara halus, dan rambut memberikan gerakan sekunder yang memperkuat kepribadian visualnya.

Seluruh artwork dibuat menggunakan HTML, inline SVG, CSS animation, dan JavaScript native tanpa library eksternal. Desainnya responsif, memiliki background transparan, serta mendukung reduced motion.

Lihat animasinya, eksplorasi detail geometrinya, dan coba source code lengkap untuk mempelajari bagaimana karakter SVG dapat dibuat lebih ekspresif!

Hashtag:

#SVGAnimation #CartoonCharacter #CreativeCoding #AnimatedSVG #CharacterAnimation #DigitalIllustration #SVGArt #CSSAnimation #JavaScriptAnimation #VectorIllustration #MotionDesign #InteractiveSVG #FrontendDevelopment #CharacterRigging #WebAnimation

Ditulis dan diedit oleh Ahmad Ismail Al Malik

Ahmad Ismail, S.Kom Founder ALMalik Developer. Software engineer dan web architect yang menulis tutorial HTML, CSS, JavaScript, PHP, Python, dan Android untuk pemula hingga profesional.

Komentar

Belum ada komentar. Tulis pertanyaan atau pengalaman Anda di bawah.