Example scrolling GIF

#!/bin/bash

IMWIDTH=336
IMHEIGHT=24
STEP=2 #pixels per frame
FRAMERATE=50 #frames per second

OUTPUT="scroll.gif"
FONT_PATH=/usr/share/fonts/TTF/LiberationSans-Bold.ttf

MESSAGE="Hello World!"

BACKGROUND="#000000"
FILL="#ff9c10"

# Make a "unique" prefix for this run
PREFIX="$(head -c 32 /dev/urandom  | shasum | cut -b 1-10)"

# Generate image from text input
convert -background "$BACKGROUND" -fill "$FILL" -font "$FONT_PATH" -density 200 -pointsize 100 "label:${MESSAGE}" "/tmp/${PREFIX}_label.png"

# Resize to 128px high
convert -resize x${IMHEIGHT} "/tmp/${PREFIX}_label.png" "/tmp/${PREFIX}_sized.png"

# Add white space to front and back for empty frames
WIDTH="$(identify -format "%[fx:w]" "/tmp/${PREFIX}_sized.png")"
CANVAS_SIZE=$(($WIDTH + $IMWIDTH + $IMWIDTH))
convert -size ${CANVAS_SIZE}x${IMHEIGHT} "xc:$BACKGROUND" "/tmp/${PREFIX}_canvas.png"
convert "/tmp/${PREFIX}_canvas.png" "/tmp/${PREFIX}_sized.png" -geometry +${IMWIDTH}+0 -composite "/tmp/${PREFIX}_padded.png"

# Generate individual frames
OFFSET=0
I=0
LIMIT=$(($CANVAS_SIZE - $IMWIDTH))
while [ $OFFSET -lt $LIMIT ]; do
  echo ${OFFSET}/${LIMIT}
  convert "/tmp/${PREFIX}_padded.png" -crop "${IMWIDTH}x${IMHEIGHT}+${OFFSET}+0!" "PNG32:$(printf "/tmp/${PREFIX}_frame_%05d.png" $I)"
  I=$(($I + 1))
  OFFSET=$(($OFFSET + $STEP))
done

# Compile to gif
ffmpeg -y -f image2 -i "/tmp/${PREFIX}_frame_%05d.png" -vf palettegen "/tmp/${PREFIX}_frame_palette.png"
ffmpeg -y -f image2 -framerate $FRAMERATE -i "/tmp/${PREFIX}_frame_%05d.png" -i "/tmp/${PREFIX}_frame_palette.png" -filter_complex "[0:v][1:v]paletteuse" "$OUTPUT"

# Compress it
gifsicle -bO --dither --colors 8 "$OUTPUT"

# Clean up!
rm /tmp/${PREFIX}_*.png

Adapted from https://gist.github.com/jmhobbs/b6ba8f5d1f816506e5b671c1bd89c723